diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2dd8a08255d..3d58135095b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "github.vscode-pull-request-github", "ms-vscode.vscode-github-issue-notebooks", "ms-vscode.vscode-selfhost-test-provider", - "ms-vscode.extension-test-runner" + "ms-vscode.extension-test-runner", + "jrieken.vscode-pr-pinger" ] } diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index be9908640bf..e2d8331cc77 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"October 2023\"\n" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"November 2023\"\n" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 2e1c21bdb08..1b17b312fea 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-pylint repo:microsoft/vscode-black-formatter repo:microsoft/vscode-isort repo:microsoft/lsprotocol repo:microsoft/vscode-flake8 repo:microsoft/vscode-autopep8 repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-mypy repo:microsoft/vscode-python-debugger\n\n$MILESTONE=milestone:\"October 2023\"\n" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-pylint repo:microsoft/vscode-black-formatter repo:microsoft/vscode-isort repo:microsoft/lsprotocol repo:microsoft/vscode-flake8 repo:microsoft/vscode-autopep8 repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-mypy repo:microsoft/vscode-python-debugger\n\n$MILESTONE=milestone:\"November 2023\"\n" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 0ae6c217798..0b3a721b43f 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-pylint repo:microsoft/vscode-black-formatter repo:microsoft/vscode-isort repo:microsoft/lsprotocol repo:microsoft/vscode-flake8 repo:microsoft/vscode-autopep8 repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-mypy repo:microsoft/vscode-python-debugger\n\n$MILESTONE=milestone:\"October 2023\"\n\n$MINE=assignee:@me\n" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-pylint repo:microsoft/vscode-black-formatter repo:microsoft/vscode-isort repo:microsoft/lsprotocol repo:microsoft/vscode-flake8 repo:microsoft/vscode-autopep8 repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-mypy repo:microsoft/vscode-python-debugger\n\n$MILESTONE=milestone:\"November 2023\"\n\n$MINE=assignee:@me\n" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index f1e618d194f..68aa2f090e7 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release\n\n// current milestone name\n$milestone=milestone:\"October 2023\"\n" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release\n\n// current milestone name\n$milestone=milestone:\"November 2023\"\n" }, { "kind": 1, diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index f0b318a28d1..6e19a79afb9 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"October 2023\"\n$closedRecently=closed:>2023-09-29\n" + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"November 2023\"\n$closedRecently=closed:>2023-09-29\n" }, { "kind": 1, diff --git a/.vscode/notebooks/vscode-dev.github-issues b/.vscode/notebooks/vscode-dev.github-issues index 57c7b73bb91..314a541823f 100644 --- a/.vscode/notebooks/vscode-dev.github-issues +++ b/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"October 2023\"\n" + "value": "$milestone=milestone:\"November 2023\"\n" }, { "kind": 1, diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 0c8914b74e8..8d641861217 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-markdown-languageserver", "description": "Markdown language server", - "version": "0.4.0-alpha.7", + "version": "0.4.0-alpha.8", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -18,7 +18,7 @@ "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.4.0-alpha.7", + "vscode-markdown-languageservice": "^0.4.0-alpha.8", "vscode-uri": "^3.0.7" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index c3b96b3ec10..fcb85f9a94b 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -128,10 +128,10 @@ vscode-languageserver@^8.1.0: dependencies: vscode-languageserver-protocol "3.17.3" -vscode-markdown-languageservice@^0.4.0-alpha.7: - version "0.4.0-alpha.7" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.7.tgz#485e3394ff792a6fb225e5323ba9b5170f6fa7e7" - integrity sha512-GZ8e1ewU989nLSRyQp3wXjIBFWZnu/rfo4gJxazujU2nTkR+VhPGh/ar/5pX7iHLRbESvcqgVqRQNU7zzVo/hw== +vscode-markdown-languageservice@^0.4.0-alpha.8: + version "0.4.0-alpha.8" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.8.tgz#5c3aaf3b3cd3f6b309f8dfcf93cbfb1397041adc" + integrity sha512-6S6RE5s+4biWg2xk9bpwNi6GihUYQIVxdO3I+jb/XDyvfmqYVxrN86cKLF8QSbaQvX3fMuBAxBLFfX93FdJi3w== dependencies: "@vscode/l10n" "^0.0.10" node-html-parser "^6.1.5" diff --git a/extensions/typescript-language-features/src/tsconfig.ts b/extensions/typescript-language-features/src/tsconfig.ts index f5f91b23620..196cf185170 100644 --- a/extensions/typescript-language-features/src/tsconfig.ts +++ b/extensions/typescript-language-features/src/tsconfig.ts @@ -27,7 +27,7 @@ export function inferredProjectCompilerOptions( ): Proto.ExternalProjectCompilerOptions { const projectConfig: Proto.ExternalProjectCompilerOptions = { module: 'ESNext' as Proto.ModuleKind, - moduleResolution: (version.gte(API.v500) ? 'Bundler' : 'Node') as Proto.ModuleResolutionKind, + moduleResolution: 'Node' as Proto.ModuleResolutionKind, target: 'ES2022' as Proto.ScriptTarget, jsx: 'react' as Proto.JsxEmit, }; diff --git a/package.json b/package.json index d850ee9300e..4736c4dbca5 100644 --- a/package.json +++ b/package.json @@ -96,14 +96,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.4.0-beta.32", - "xterm-addon-canvas": "0.6.0-beta.32", + "xterm": "5.4.0-beta.37", + "xterm-addon-canvas": "0.6.0-beta.37", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.31", - "xterm-addon-serialize": "0.12.0-beta.31", - "xterm-addon-unicode11": "0.7.0-beta.31", - "xterm-addon-webgl": "0.17.0-beta.31", - "xterm-headless": "5.4.0-beta.32", + "xterm-addon-search": "0.14.0-beta.36", + "xterm-addon-serialize": "0.12.0-beta.36", + "xterm-addon-unicode11": "0.7.0-beta.36", + "xterm-addon-webgl": "0.17.0-beta.36", + "xterm-headless": "5.4.0-beta.37", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 8c2e24e4fac..50e021d504e 100644 --- a/remote/package.json +++ b/remote/package.json @@ -26,14 +26,14 @@ "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.0.0", - "xterm": "5.4.0-beta.32", - "xterm-addon-canvas": "0.6.0-beta.32", + "xterm": "5.4.0-beta.37", + "xterm-addon-canvas": "0.6.0-beta.37", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.31", - "xterm-addon-serialize": "0.12.0-beta.31", - "xterm-addon-unicode11": "0.7.0-beta.31", - "xterm-addon-webgl": "0.17.0-beta.31", - "xterm-headless": "5.4.0-beta.32", + "xterm-addon-search": "0.14.0-beta.36", + "xterm-addon-serialize": "0.12.0-beta.36", + "xterm-addon-unicode11": "0.7.0-beta.36", + "xterm-addon-webgl": "0.17.0-beta.36", + "xterm-headless": "5.4.0-beta.37", "yauzl": "^2.9.2", "yazl": "^2.4.3" } diff --git a/remote/web/package.json b/remote/web/package.json index 2634c6ff3b8..6570ba0ff0a 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -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.32", - "xterm-addon-canvas": "0.6.0-beta.32", + "xterm": "5.4.0-beta.37", + "xterm-addon-canvas": "0.6.0-beta.37", "xterm-addon-image": "0.6.0-beta.21", - "xterm-addon-search": "0.14.0-beta.31", - "xterm-addon-unicode11": "0.7.0-beta.31", - "xterm-addon-webgl": "0.17.0-beta.31" + "xterm-addon-search": "0.14.0-beta.36", + "xterm-addon-unicode11": "0.7.0-beta.36", + "xterm-addon-webgl": "0.17.0-beta.36" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 6eb7304ba80..4d7cf110e03 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -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.32: - version "0.6.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" - integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== +xterm-addon-canvas@0.6.0-beta.37: + version "0.6.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.37.tgz#4f7692fc87f3500a5a6e7f822ec74796e6a36db2" + integrity sha512-eFN7iPxqiQosUO+rbXKwPz/AR3TLJcLCVWjvT5EaewqNpw+E9rWVwRRPGqYSw+qkSKM/vcNlySIkq4hmpA/ohg== 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.31: - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" - integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== +xterm-addon-search@0.14.0-beta.36: + version "0.14.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.36.tgz#4bb161130c39090d506f73468ef65054aedcb9f0" + integrity sha512-2ydZV1Nt8d0vOjZ/y2RXiw3QLgabK+PCA7yETqZNivnmhSfcxtYPyI4aRfG/lc3hoMKiPppSSjJF0MlV/qo8ww== -xterm-addon-unicode11@0.7.0-beta.31: - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" - integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== +xterm-addon-unicode11@0.7.0-beta.36: + version "0.7.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.36.tgz#bde0b8c98211b74b0288cd5bad09107384b870ca" + integrity sha512-vV/f7dTP/46JjoJLYDCxolCWPoUn0cF7S5upYvY0ZA1VLQ9UnXmaNjzrg+9f+QBoXEQ2eQDLQcRFWOc6BAbF+A== -xterm-addon-webgl@0.17.0-beta.31: - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" - integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== +xterm-addon-webgl@0.17.0-beta.36: + version "0.17.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.36.tgz#6ed16a83cd4bed3cbddd631e7974375aa8f7e023" + integrity sha512-5PNQZcNH2UTaqSf7Exgh/fvsNeTOs+CSEuIaTafzKuMIrWH14+cRdyJdzyo8OKTNzhJOjwWDOTQTD/sur4lrLg== -xterm@5.4.0-beta.32: - version "5.4.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" - integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== +xterm@5.4.0-beta.37: + version "5.4.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.37.tgz#bae127be8c939f096232d9858e77a457d6b77ddf" + integrity sha512-ys+mXqLFrJc7khmYN/MgBnfLv38NgXfkwkEXsCZKHGqn3h2xUBvTvsrSEWO3NQeDPLj4zMr1RwqTblMK9St3BA== diff --git a/remote/yarn.lock b/remote/yarn.lock index b0446180fb5..d4ffc6f9d28 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -589,45 +589,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.32: - version "0.6.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" - integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== +xterm-addon-canvas@0.6.0-beta.37: + version "0.6.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.37.tgz#4f7692fc87f3500a5a6e7f822ec74796e6a36db2" + integrity sha512-eFN7iPxqiQosUO+rbXKwPz/AR3TLJcLCVWjvT5EaewqNpw+E9rWVwRRPGqYSw+qkSKM/vcNlySIkq4hmpA/ohg== 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.31: - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" - integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== +xterm-addon-search@0.14.0-beta.36: + version "0.14.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.36.tgz#4bb161130c39090d506f73468ef65054aedcb9f0" + integrity sha512-2ydZV1Nt8d0vOjZ/y2RXiw3QLgabK+PCA7yETqZNivnmhSfcxtYPyI4aRfG/lc3hoMKiPppSSjJF0MlV/qo8ww== -xterm-addon-serialize@0.12.0-beta.31: - version "0.12.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.31.tgz#2a95dc1e12f4097e2894b04c9cb8fff0bc0b858c" - integrity sha512-h2rWR+Lfi1Iv4VkLUlrBMYh5Mdq8vux2BKyCJe6a1ZnEu5Dzb0VuiNxfTKXTCT5M83nMn7TCB9TX0E8z6bs7xw== +xterm-addon-serialize@0.12.0-beta.36: + version "0.12.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.36.tgz#8e74159317e2c1e5f2724fd92b85df2b87d2c9c9" + integrity sha512-J2mr3Wuxzvxke14IpfJBhwCHlWoB428FE06tOfbGi5lCBm0AnO2cIyRymvgVZG4QVXVxzzWroE8wP/WQQVe45w== -xterm-addon-unicode11@0.7.0-beta.31: - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" - integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== +xterm-addon-unicode11@0.7.0-beta.36: + version "0.7.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.36.tgz#bde0b8c98211b74b0288cd5bad09107384b870ca" + integrity sha512-vV/f7dTP/46JjoJLYDCxolCWPoUn0cF7S5upYvY0ZA1VLQ9UnXmaNjzrg+9f+QBoXEQ2eQDLQcRFWOc6BAbF+A== -xterm-addon-webgl@0.17.0-beta.31: - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" - integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== +xterm-addon-webgl@0.17.0-beta.36: + version "0.17.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.36.tgz#6ed16a83cd4bed3cbddd631e7974375aa8f7e023" + integrity sha512-5PNQZcNH2UTaqSf7Exgh/fvsNeTOs+CSEuIaTafzKuMIrWH14+cRdyJdzyo8OKTNzhJOjwWDOTQTD/sur4lrLg== -xterm-headless@5.4.0-beta.32: - version "5.4.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.32.tgz#0d5cd35e1a0372888055ff0b06dfe17457979a6c" - integrity sha512-DQduq8KSoQZyRrQAFB+FkcY2UMxCW39P1/duOpksebc6PT9pbGkyPe5s+AdUQGiYzriEpzVtKUzDcquoVmpPhA== +xterm-headless@5.4.0-beta.37: + version "5.4.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.37.tgz#1e89dfa7e667a5951974a9e9b5e1b0875198ca65" + integrity sha512-iP28Z419p7anaNetLhmPOpt/DUdeA45jVLqoTn8bRpwFj8kM/gI76tUO9y/TyMfv79yWwPv2MrkALfS6ZyTi7g== -xterm@5.4.0-beta.32: - version "5.4.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" - integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== +xterm@5.4.0-beta.37: + version "5.4.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.37.tgz#bae127be8c939f096232d9858e77a457d6b77ddf" + integrity sha512-ys+mXqLFrJc7khmYN/MgBnfLv38NgXfkwkEXsCZKHGqn3h2xUBvTvsrSEWO3NQeDPLj4zMr1RwqTblMK9St3BA== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 13ff65cfb4e..0fd241f3313 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -18,23 +18,38 @@ import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { hash } from 'vs/base/common/hash'; +type WindowGlobal = Window & typeof globalThis; + +interface IWindow { + readonly window: WindowGlobal; + readonly disposables: DisposableStore; +} + export const { registerWindow, getWindows, getWindowsCount, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () { - const windows = new Set([window]); - const onDidRegisterWindow = new event.Emitter<{ window: Window & typeof globalThis; disposables: DisposableStore }>(); - const onDidUnregisterWindow = new event.Emitter(); - const onWillUnregisterWindow = new event.Emitter(); + const windows = new Map(); + windows.set(window, { window, disposables: new DisposableStore() }); + + const onDidRegisterWindow = new event.Emitter(); + const onDidUnregisterWindow = new event.Emitter(); + const onWillUnregisterWindow = new event.Emitter(); + return { onDidRegisterWindow: onDidRegisterWindow.event, onWillUnregisterWindow: onWillUnregisterWindow.event, onDidUnregisterWindow: onDidUnregisterWindow.event, - registerWindow(window: Window & typeof globalThis): IDisposable { + registerWindow(window: WindowGlobal): IDisposable { if (windows.has(window)) { return Disposable.None; } - windows.add(window); - const disposables = new DisposableStore(); + + const registeredWindow = { + window, + disposables: disposables.add(new DisposableStore()) + }; + windows.set(window, registeredWindow); + disposables.add(toDisposable(() => { windows.delete(window); onDidUnregisterWindow.fire(window); @@ -44,14 +59,12 @@ export const { registerWindow, getWindows, getWindowsCount, onDidRegisterWindow, onWillUnregisterWindow.fire(window); })); - const eventDisposables = new DisposableStore(); - disposables.add(eventDisposables); - onDidRegisterWindow.fire({ window, disposables: eventDisposables }); + onDidRegisterWindow.fire(registeredWindow); return disposables; }, - getWindows(): Iterable { - return windows; + getWindows(): Iterable { + return windows.values(); }, getWindowsCount(): number { return windows.size; @@ -163,14 +176,14 @@ export function addDisposableGenericMouseUpListener(node: EventTarget, handler: * If currently in an animation frame, `runner` will be executed immediately. * @return token that can be used to cancel the scheduled runner (only if `runner` was not executed immediately). */ -export let runAtThisOrScheduleAtNextAnimationFrame: (runner: () => void, priority?: number) => IDisposable; +export let runAtThisOrScheduleAtNextAnimationFrame: (runner: () => void, targetWindow: Window, priority?: number) => IDisposable; /** * Schedule a callback to be run at the next animation frame. * This allows multiple parties to register callbacks that should run at the next animation frame. * If currently in an animation frame, `runner` will be executed at the next animation frame. * @return token that can be used to cancel the scheduled runner. */ -export let scheduleAtNextAnimationFrame: (runner: () => void, priority?: number) => IDisposable; +export let scheduleAtNextAnimationFrame: (runner: () => void, targetWindow: Window, priority?: number) => IDisposable; class AnimationFrameQueueItem implements IDisposable { @@ -239,35 +252,35 @@ class AnimationFrameQueueItem implements IDisposable { inAnimationFrameRunner = false; }; - scheduleAtNextAnimationFrame = (runner: () => void, priority: number = 0) => { + scheduleAtNextAnimationFrame = (runner: () => void, targetWindow: Window, priority: number = 0) => { const item = new AnimationFrameQueueItem(runner, priority); NEXT_QUEUE.push(item); if (!animFrameRequested) { animFrameRequested = true; - requestAnimationFrame(animationFrameRunner); + targetWindow.requestAnimationFrame(animationFrameRunner); } return item; }; - runAtThisOrScheduleAtNextAnimationFrame = (runner: () => void, priority?: number) => { + runAtThisOrScheduleAtNextAnimationFrame = (runner: () => void, targetWindow: Window, priority?: number) => { if (inAnimationFrameRunner) { const item = new AnimationFrameQueueItem(runner, priority); CURRENT_QUEUE!.push(item); return item; } else { - return scheduleAtNextAnimationFrame(runner, priority); + return scheduleAtNextAnimationFrame(runner, targetWindow, priority); } }; })(); -export function measure(callback: () => void): IDisposable { - return scheduleAtNextAnimationFrame(callback, 10000 /* must be early */); +export function measure(callback: () => void, targetWindow: Window): IDisposable { + return scheduleAtNextAnimationFrame(callback, targetWindow, 10000 /* must be early */); } -export function modify(callback: () => void): IDisposable { - return scheduleAtNextAnimationFrame(callback, -10000 /* must be late */); +export function modify(callback: () => void, targetWindow: Window): IDisposable { + return scheduleAtNextAnimationFrame(callback, targetWindow, -10000 /* must be late */); } /** @@ -758,25 +771,24 @@ export function getActiveDocument(): Document { return document; } - const documents = Array.from(getWindows()).map(window => window.document); - return documents.find(doc => doc.hasFocus()) ?? document; + const documents = Array.from(getWindows()).map(({ window }) => window.document); + return documents.find(document => document.hasFocus()) ?? document; } -export function getActiveWindow(): Window & typeof globalThis { +export function getActiveWindow(): WindowGlobal { const document = getActiveDocument(); return document.defaultView?.window ?? window; } -export function getWindow(element: Node): Window & typeof globalThis; -export function getWindow(event: UIEvent): Window & typeof globalThis; -export function getWindow(obj: unknown): Window & typeof globalThis; -export function getWindow(e: unknown): Window & typeof globalThis { - const candidateNode = e as Node | undefined; +export function getWindow(element: Node | undefined | null): WindowGlobal; +export function getWindow(event: UIEvent | undefined | null): WindowGlobal; +export function getWindow(e: unknown): WindowGlobal { + const candidateNode = e as Node | undefined | null; if (candidateNode?.ownerDocument?.defaultView) { return candidateNode.ownerDocument.defaultView.window; } - const candidateEvent = e as UIEvent | undefined; + const candidateEvent = e as UIEvent | undefined | null; if (candidateEvent?.view) { return candidateEvent.view.window; } @@ -805,19 +817,13 @@ export function createStyleSheet(container: HTMLElement = document.head, beforeA // With as container, the stylesheet becomes global and is tracked // to support auxiliary windows to clone the stylesheet. if (container === document.head) { - for (const targetWindow of getWindows()) { + for (const { window: targetWindow, disposables } of getWindows()) { if (targetWindow === window) { continue; // main window is already tracked } - const cloneDisposable = cloneGlobalStyleSheet(style, targetWindow); + const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, targetWindow)); disposableStore?.add(cloneDisposable); - - disposableStore?.add(event.Event.once(onDidUnregisterWindow)(unregisteredWindow => { - if (unregisteredWindow === targetWindow) { - cloneDisposable.dispose(); - } - })); } } @@ -989,22 +995,22 @@ function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { export function isMouseEvent(e: unknown): e is MouseEvent { // eslint-disable-next-line no-restricted-syntax - return e instanceof MouseEvent || e instanceof getWindow(e).MouseEvent; + return e instanceof MouseEvent || e instanceof getWindow(e as UIEvent).MouseEvent; } export function isKeyboardEvent(e: unknown): e is KeyboardEvent { // eslint-disable-next-line no-restricted-syntax - return e instanceof KeyboardEvent || e instanceof getWindow(e).KeyboardEvent; + return e instanceof KeyboardEvent || e instanceof getWindow(e as UIEvent).KeyboardEvent; } export function isPointerEvent(e: unknown): e is PointerEvent { // eslint-disable-next-line no-restricted-syntax - return e instanceof PointerEvent || e instanceof getWindow(e).PointerEvent; + return e instanceof PointerEvent || e instanceof getWindow(e as UIEvent).PointerEvent; } export function isDragEvent(e: unknown): e is DragEvent { // eslint-disable-next-line no-restricted-syntax - return e instanceof DragEvent || e instanceof getWindow(e).DragEvent; + return e instanceof DragEvent || e instanceof getWindow(e as UIEvent).DragEvent; } export const EventType = { @@ -1456,13 +1462,13 @@ export function windowOpenWithSuccess(url: string, noOpener = true): boolean { return false; } -export function animate(fn: () => void): IDisposable { +export function animate(fn: () => void, targetWindow: Window): IDisposable { const step = () => { fn(); - stepDisposable = scheduleAtNextAnimationFrame(step); + stepDisposable = scheduleAtNextAnimationFrame(step, targetWindow); }; - let stepDisposable = scheduleAtNextAnimationFrame(step); + let stepDisposable = scheduleAtNextAnimationFrame(step, targetWindow); return toDisposable(() => stepDisposable.dispose()); } diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index dd74dca3eb4..23607237cd2 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -93,7 +93,7 @@ export class Gesture extends Disposable { this._register(EventUtils.runAndSubscribe(DomUtils.onDidRegisterWindow, ({ window, disposables }) => { disposables.add(DomUtils.addDisposableListener(window.document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false })); - disposables.add(DomUtils.addDisposableListener(window.document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e))); + disposables.add(DomUtils.addDisposableListener(window.document, 'touchend', (e: TouchEvent) => this.onTouchEnd(window, e))); disposables.add(DomUtils.addDisposableListener(window.document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false })); }, { window, disposables: this._store })); } @@ -173,7 +173,7 @@ export class Gesture extends Disposable { } } - private onTouchEnd(e: TouchEvent): void { + private onTouchEnd(targetWindow: Window, e: TouchEvent): void { const timestamp = Date.now(); // use Date.now() because on FF e.timeStamp is not epoch based. const activeTouchCount = Object.keys(this.activeTouches).length; @@ -218,13 +218,13 @@ export class Gesture extends Disposable { // We need to get all the dispatch targets on the start of the inertia event const dispatchTo = [...this.targets].filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget)); - this.inertia(dispatchTo, timestamp, // time now - Math.abs(deltaX) / deltaT, // speed - deltaX > 0 ? 1 : -1, // x direction - finalX, // x now - Math.abs(deltaY) / deltaT, // y speed - deltaY > 0 ? 1 : -1, // y direction - finalY // y now + this.inertia(targetWindow, dispatchTo, timestamp, // time now + Math.abs(deltaX) / deltaT, // speed + deltaX > 0 ? 1 : -1, // x direction + finalX, // x now + Math.abs(deltaY) / deltaT, // y speed + deltaY > 0 ? 1 : -1, // y direction + finalY // y now ); } @@ -282,7 +282,7 @@ export class Gesture extends Disposable { } } - private inertia(dispatchTo: readonly EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void { + private inertia(targetWindow: Window, dispatchTo: readonly EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void { this.handle = DomUtils.scheduleAtNextAnimationFrame(() => { const now = Date.now(); @@ -311,9 +311,9 @@ export class Gesture extends Disposable { dispatchTo.forEach(d => d.dispatchEvent(evt)); if (!stopped) { - this.inertia(dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y); + this.inertia(targetWindow, dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y); } - }); + }, targetWindow); } private onTouchMove(e: TouchEvent): void { diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 3775611c73a..6c4edfef56d 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -128,7 +128,7 @@ export class BreadcrumbsWidget { this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; disposables.add(this._updateScrollbar()); - })); + }, dom.getWindow(this._domNode))); return disposables; } @@ -138,8 +138,8 @@ export class BreadcrumbsWidget { this._scrollable.setRevealOnScroll(false); this._scrollable.scanDomNode(); this._scrollable.setRevealOnScroll(true); - }); - }); + }, dom.getWindow(this._domNode)); + }, dom.getWindow(this._domNode)); } private _style(styleElement: HTMLStyleElement, style: IBreadcrumbsWidgetStyles): void { diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index da375f17187..e6c8f4987be 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -617,16 +617,22 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge readonly onDidBlur = this._onDidBlur.event; constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions) { - const NLS_PLACEHOLDER_HISTORY_HINT = nls.localize({ key: 'history.inputbox.hint', comment: ['Text will be prefixed with \u21C5 plus a single space, then used as a hint where input field keeps history'] }, "for history"); - const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX = ` or \u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT}`; - const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = ` (\u21C5 ${NLS_PLACEHOLDER_HISTORY_HINT})`; + const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_NO_PARENS = nls.localize({ + key: 'history.inputbox.hint.suffix.noparens', + comment: ['Text is the suffix of an input field placeholder coming after the action the input field performs, this will be used when the input field ends in a closing parenthesis ")", for example "Filter (e.g. text, !exclude)". The character inserted into the final string is \u21C5 to represent the up and down arrow keys.'] + }, ' or {0} for history', `\u21C5`); + const NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS = nls.localize({ + key: 'history.inputbox.hint.suffix.inparens', + comment: ['Text is the suffix of an input field placeholder coming after the action the input field performs, this will be used when the input field does NOT end in a closing parenthesis (eg. "Find"). The character inserted into the final string is \u21C5 to represent the up and down arrow keys.'] + }, ' ({0} for history)', `\u21C5`); + super(container, contextViewProvider, options); this.history = new HistoryNavigator(options.history, 100); // Function to append the history suffix to the placeholder if necessary const addSuffix = () => { - if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) { - const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS; + if (options.showHistoryHint && options.showHistoryHint() && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_NO_PARENS) && !this.placeholder.endsWith(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS) && this.history.getHistory().length) { + const suffix = this.placeholder.endsWith(')') ? NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_NO_PARENS : NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS; const suffixedPlaceholder = this.placeholder + suffix; if (options.showPlaceholderOnFocus && !dom.isActiveElement(this.input)) { this.placeholder = suffixedPlaceholder; @@ -666,7 +672,7 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge } }; if (!resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_IN_PARENS)) { - resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX); + resetPlaceholder(NLS_PLACEHOLDER_HISTORY_HINT_SUFFIX_NO_PARENS); } }); } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9090f95fa79..24af99d7e50 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; @@ -407,7 +407,7 @@ export class ListView implements IListView { this.scrollable = this.disposables.add(new Scrollable({ forceIntegerValues: true, smoothScrollDuration: (options.smoothScrolling ?? false) ? 125 : 0, - scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb) + scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb, getWindow(this.domNode)) })); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: options.alwaysConsumeMouseWheel ?? DefaultOptions.alwaysConsumeMouseWheel, @@ -684,7 +684,7 @@ export class ListView implements IListView { this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight }); this.updateScrollWidth(); this.scrollableElementUpdateDisposable = null; - }); + }, getWindow(this.domNode)); } } @@ -1291,7 +1291,7 @@ export class ListView implements IListView { private setupDragAndDropScrollTopAnimation(event: DragEvent): void { if (!this.dragOverAnimationDisposable) { const viewTop = getTopLeftOffset(this.domNode).top; - this.dragOverAnimationDisposable = animate(this.animateDragAndDropScrollTop.bind(this, viewTop)); + this.dragOverAnimationDisposable = animate(this.animateDragAndDropScrollTop.bind(this, viewTop), getWindow(this.domNode)); } this.dragOverAnimationStopDisposable.dispose(); diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index ce52caea6f1..58b3f29af0f 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -630,7 +630,7 @@ export class MenuBar extends Disposable { this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(() => { this.updateOverflowAction(); this.overflowLayoutScheduled = undefined; - }); + }, DOM.getWindow(this.container)); } this.setUnfocusedState(); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 6cb673170c8..ac02cd0c567 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -576,7 +576,7 @@ export class ScrollableElement extends AbstractScrollableElement { const scrollable = new Scrollable({ forceIntegerValues: true, smoothScrollDuration: 0, - scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback) + scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(element)) }); super(element, options, scrollable); this._register(scrollable); @@ -621,7 +621,7 @@ export class DomScrollableElement extends AbstractScrollableElement { const scrollable = new Scrollable({ forceIntegerValues: false, // See https://github.com/microsoft/vscode/issues/139877 smoothScrollDuration: 0, - scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback) + scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(element)) }); super(element, options, scrollable); this._register(scrollable); diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 8f63b6039a1..e88947b044e 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, addDisposableListener, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { ISashEvent as IBaseSashEvent, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -584,7 +584,7 @@ export class SplitView scheduleAtNextAnimationFrame(callback, getWindow(this.el)), })); this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, { vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden, diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index f6b344f1872..a7da5f289ed 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -6,6 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IDimension } from 'vs/editor/common/core/dimension'; import { Emitter, Event } from 'vs/base/common/event'; +import { getWindow, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; export class ElementSizeObserver extends Disposable { @@ -65,10 +66,10 @@ export class ElementSizeObserver extends Disposable { alreadyObservedThisAnimationFrame = true; observeNow(); } finally { - requestAnimationFrame(() => { + scheduleAtNextAnimationFrame(() => { alreadyObservedThisAnimationFrame = false; update(); - }); + }, getWindow(this._referenceDomElement)); } } }; diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index cc23b026471..6796378114d 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -682,7 +682,7 @@ class TopBottomDragScrollingOperation extends Disposable { this._position = position; this._mouseEvent = mouseEvent; this._lastTime = Date.now(); - this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute()); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute(), dom.getWindow(mouseEvent.browserEvent)); } public override dispose(): void { @@ -752,7 +752,7 @@ class TopBottomDragScrollingOperation extends Disposable { } this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None); - this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute()); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute(), dom.getWindow(mouseTarget.element)); } } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index ff4b7514f27..700b1d55b1d 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -432,7 +432,7 @@ export class View extends ViewEventHandler { private _scheduleRender(): void { if (this._renderAnimationFrame === null) { - this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100); + this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), dom.getWindow(this.domNode.domNode), 100); } } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index a09df64b633..36506e9fed0 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -146,7 +146,7 @@ export class ViewCursor { return null; } - const window = dom.getWindow(this._domNode); + const window = dom.getWindow(this._domNode.domNode); let width: number; if (this._cursorStyle === TextEditorCursorStyle.Line) { width = dom.computeScreenAwareSize(window, this._lineCursorWidth > 0 ? this._lineCursorWidth : 2); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index c1ea768376a..93407998bc0 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1648,7 +1648,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE model, DOMLineBreaksComputerFactory.create(), MonospaceLineBreaksComputerFactory.create(this._configuration.options), - (callback) => dom.scheduleAtNextAnimationFrame(callback), + (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(this._domElement)), this.languageConfigurationService, this._themeService, attachedView, diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index b125fb00c01..1a4a07758c5 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, h } from 'vs/base/browser/dom'; +import { $, getWindow, h } from 'vs/base/browser/dom'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { findLast } from 'vs/base/common/arraysFind'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -178,6 +178,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { /** @description ViewZoneManager */ store.add(this._instantiationService.createInstance( readHotReloadableExport(ViewZoneManager, reader), + getWindow(this._domElement), this._editors, this._diffModel, this._options, diff --git a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts index 893e4000c93..698734aeba0 100644 --- a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts @@ -42,14 +42,15 @@ export class ViewZoneManager extends Disposable { private readonly _originalTopPadding = observableValue(this, 0); private readonly _originalScrollTop: IObservable; private readonly _originalScrollOffset = observableValue(this, 0); - private readonly _originalScrollOffsetAnimated = animatedObservable(this._originalScrollOffset, this._store); + private readonly _originalScrollOffsetAnimated = animatedObservable(this._targetWindow, this._originalScrollOffset, this._store); private readonly _modifiedTopPadding = observableValue(this, 0); private readonly _modifiedScrollTop: IObservable; private readonly _modifiedScrollOffset = observableValue(this, 0); - private readonly _modifiedScrollOffsetAnimated = animatedObservable(this._modifiedScrollOffset, this._store); + private readonly _modifiedScrollOffsetAnimated = animatedObservable(this._targetWindow, this._modifiedScrollOffset, this._store); constructor( + private readonly _targetWindow: Window, private readonly _editors: DiffEditorEditors, private readonly _diffModel: IObservable, private readonly _options: DiffEditorOptions, diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index 352ff672edc..81b00083ac7 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -123,7 +123,7 @@ export class ObservableElementSizeObserver extends Disposable { } } -export function animatedObservable(base: IObservable, store: DisposableStore): IObservable { +export function animatedObservable(targetWindow: Window, base: IObservable, store: DisposableStore): IObservable { let targetVal = base.get(); let startVal = targetVal; let curVal = targetVal; @@ -160,7 +160,7 @@ export function animatedObservable(base: IObservable, store: Di curVal = Math.floor(easeOutExpo(passedMs, startVal, targetVal - startVal, durationMs)); if (passedMs < durationMs) { - animationFrame = requestAnimationFrame(update); + animationFrame = targetWindow.requestAnimationFrame(update); } else { curVal = targetVal; } diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index b86e2fd94ef..aa78d74936a 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -571,7 +571,7 @@ export class SuggestWidget implements IDisposable { this._layout(this.element.size); // Reset focus border this._details.widget.domNode.classList.remove('focused'); - }); + }, dom.getWindow(this.element.domNode)); } focusSelected(): void { @@ -721,7 +721,7 @@ export class SuggestWidget implements IDisposable { this._positionDetails(); this.editor.focus(); this.element.domNode.classList.add('shows-details'); - }); + }, dom.getWindow(this.element.domNode)); } toggleExplainMode(): void { diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts index 2c45263ca24..28cde30f584 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.ts @@ -41,6 +41,7 @@ class EditorScopedQuickInputService extends QuickInputService { _serviceBrand: undefined, get hasContainer() { return true; }, get container() { return widget.getDomNode(); }, + getContainer() { return widget.getDomNode(); }, get containers() { return [widget.getDomNode()]; }, get activeContainer() { return widget.getDomNode(); }, get mainContainerDimension() { return editor.getLayoutInfo(); }, diff --git a/src/vs/editor/standalone/browser/standaloneLayoutService.ts b/src/vs/editor/standalone/browser/standaloneLayoutService.ts index 86aeb14e2ab..ae901c68b41 100644 --- a/src/vs/editor/standalone/browser/standaloneLayoutService.ts +++ b/src/vs/editor/standalone/browser/standaloneLayoutService.ts @@ -54,6 +54,10 @@ class StandaloneLayoutService implements ILayoutService { return activeCodeEditor?.getContainerDomNode() ?? this.container; } + getContainer() { + return this.activeContainer; + } + focus(): void { this._codeEditorService.getFocusedCodeEditor()?.focus(); } diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 9e47434c125..c655b9f9b7a 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -79,7 +79,7 @@ export class MockScopableContextKeyService extends MockContextKeyService { * Don't implement this for all tests since we rarely depend on this behavior and it isn't implemented fully */ public override createScoped(domNote: HTMLElement): IScopedContextKeyService { - return new MockContextKeyService(); + return new MockScopableContextKeyService(); } } diff --git a/src/vs/platform/layout/browser/layoutService.ts b/src/vs/platform/layout/browser/layoutService.ts index 8ba84c5d9e5..7ebad2e5fa4 100644 --- a/src/vs/platform/layout/browser/layoutService.ts +++ b/src/vs/platform/layout/browser/layoutService.ts @@ -81,6 +81,11 @@ export interface ILayoutService { */ readonly containers: Iterable; + /** + * Get the container for the given window. + */ + getContainer(window: Window): HTMLElement; + /** * An offset to use for positioning elements inside the main container. */ diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index ed5141b1d0a..a8c58cb79e5 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -128,7 +128,7 @@ export class Menubar { this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.nativeHostMainService.pickWorkspaceAndOpen(undefined, { forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); // Recent Menu Items - this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); + this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened({ confirm: true /* ask for confirmation */ }); // Help Menu Items const youTubeUrl = this.productService.youTubeUrl; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 37e32c47bb7..71231274d90 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -377,10 +377,10 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe // Conpty could have the wrong cursor position at this point. if (!this._cursorOnNextLine() || !prompt) { this._windowsPromptPollingInProcess = true; - // Poll for 200ms until the cursor position is correct. + // Poll for 1000ms until the cursor position is correct. let i = 0; - for (; i < 20; i++) { - await timeout(10); + for (; i < 50; i++) { + await timeout(20); prompt = this._getWindowsPrompt(); if (this._store.isDisposed || !this._windowsPromptPollingInProcess || this._cursorOnNextLine() && prompt) { if (!this._windowsPromptPollingInProcess) { @@ -390,7 +390,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } } this._windowsPromptPollingInProcess = false; - if (i === 20) { + if (i >= 50) { this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows reached max attempts, ', this._cursorOnNextLine(), this._getWindowsPrompt()); } else if (prompt) { // use the regex to set the position as it's possible input has occurred @@ -428,9 +428,11 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private _cursorOnNextLine(): boolean { const lastCommand = this.commands.at(-1); + // There is only a single command, so this check is unnecessary if (!lastCommand) { - return false; + return true; } + const cursorYAbsolute = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY; // If the cursor position is within the last command, we should poll. const lastCommandYAbsolute = (lastCommand.endMarker ? lastCommand.endMarker.line : lastCommand.marker?.line) ?? -1; @@ -442,8 +444,26 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe if (!line) { return; } - // TODO: fine tune prompt regex to accomodate for unique configurtions. - return line.translateToString(true)?.match(/^(?(\(.+\)\s)?(?:PS.+>\s)|(?:[A-Z]:\\.*>))/)?.groups?.prompt; + // TODO: fine tune prompt regex to accomodate for unique configurations. + const lineText = line.translateToString(true); + if (!lineText) { + return; + } + + // PowerShell + const pwshMatch = lineText.match(/(?(\(.+\)\s)?(?:PS.+>\s?))/); + if (pwshMatch) { + let prompt = pwshMatch?.groups?.prompt; + if (lineText === prompt && prompt.endsWith('>')) { + // Conpty may not 'render' the space at the end of the prompt + prompt += ' '; + } + return prompt; + } + + // Command Prompt + const cmdMatch = lineText.match(/^(?(\(.+\)\s)?(?:[A-Z]:\\.*>))/); + return cmdMatch?.groups?.prompt; } handleGenericCommand(options?: IHandleCommandOptions): void { diff --git a/src/vs/platform/terminal/test/common/requestStore.test.ts b/src/vs/platform/terminal/test/common/requestStore.test.ts index 3d0301d55b0..a2026e91c65 100644 --- a/src/vs/platform/terminal/test/common/requestStore.test.ts +++ b/src/vs/platform/terminal/test/common/requestStore.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { fail, strictEqual } from 'assert'; -import { DisposableStore } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ConsoleLogger, ILogService } from 'vs/platform/log/common/log'; @@ -12,37 +11,30 @@ import { LogService } from 'vs/platform/log/common/logService'; import { RequestStore } from 'vs/platform/terminal/common/requestStore'; suite('RequestStore', () => { - let disposables: DisposableStore; let instantiationService: TestInstantiationService; setup(() => { - disposables = new DisposableStore(); instantiationService = new TestInstantiationService(); instantiationService.stub(ILogService, new LogService(new ConsoleLogger())); }); - teardown(() => { - instantiationService.dispose(); - disposables.dispose(); - }); - - ensureNoDisposablesAreLeakedInTestSuite(); + const store = ensureNoDisposablesAreLeakedInTestSuite(); test('should resolve requests', async () => { - const store: RequestStore<{ data: string }, { arg: string }> = disposables.add(instantiationService.createInstance(RequestStore<{ data: string }, { arg: string }>, undefined)); + const requestStore: RequestStore<{ data: string }, { arg: string }> = store.add(instantiationService.createInstance(RequestStore<{ data: string }, { arg: string }>, undefined)); let eventArgs: { requestId: number; arg: string } | undefined; - disposables.add(store.onCreateRequest(e => eventArgs = e)); - const request = store.createRequest({ arg: 'foo' }); + store.add(requestStore.onCreateRequest(e => eventArgs = e)); + const request = requestStore.createRequest({ arg: 'foo' }); strictEqual(typeof eventArgs?.requestId, 'number'); strictEqual(eventArgs?.arg, 'foo'); - store.acceptReply(eventArgs!.requestId, { data: 'bar' }); + requestStore.acceptReply(eventArgs!.requestId, { data: 'bar' }); const result = await request; strictEqual(result.data, 'bar'); }); test('should reject the promise when the request times out', async () => { - const store: RequestStore<{ data: string }, { arg: string }> = disposables.add(instantiationService.createInstance(RequestStore<{ data: string }, { arg: string }>, 1)); - const request = store.createRequest({ arg: 'foo' }); + const requestStore: RequestStore<{ data: string }, { arg: string }> = store.add(instantiationService.createInstance(RequestStore<{ data: string }, { arg: string }>, 1)); + const request = requestStore.createRequest({ arg: 'foo' }); let threw = false; try { await request; diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index 28968b4f9cc..692dbb9e44c 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -13,7 +13,7 @@ import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; * browser helper so that it can be accessed in non-electron layers. */ export function applyZoom(zoomLevel: number): void { - for (const window of getWindows()) { + for (const { window } of getWindows()) { getGlobals(window)?.webFrame?.setZoomLevel(zoomLevel); } setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index 971096dad97..25a017e19a9 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -24,6 +24,7 @@ import { IRecent, IRecentFile, IRecentFolder, IRecentlyOpened, IRecentWorkspace, import { IWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { ResourceMap } from 'vs/base/common/map'; +import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; export const IWorkspacesHistoryMainService = createDecorator('workspacesHistoryMainService'); @@ -36,7 +37,7 @@ export interface IWorkspacesHistoryMainService { addRecentlyOpened(recents: IRecent[]): Promise; getRecentlyOpened(): Promise; removeRecentlyOpened(paths: URI[]): Promise; - clearRecentlyOpened(): Promise; + clearRecentlyOpened(options?: { confirm?: boolean }): Promise; } export class WorkspacesHistoryMainService extends Disposable implements IWorkspacesHistoryMainService { @@ -54,7 +55,8 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa @ILogService private readonly logService: ILogService, @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService + @IApplicationStorageMainService private readonly applicationStorageMainService: IApplicationStorageMainService, + @IDialogMainService private readonly dialogMainService: IDialogMainService ) { super(); @@ -157,7 +159,24 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa } } - async clearRecentlyOpened(): Promise { + async clearRecentlyOpened(options?: { confirm?: boolean }): Promise { + if (options?.confirm) { + const { response } = await this.dialogMainService.showMessageBox({ + type: 'warning', + buttons: [ + localize({ key: 'clearButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Clear"), + localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&Cancel") + ], + message: localize('confirmClearRecentsMessage', "Do you want to clear all recently opened files and workspaces?"), + detail: localize('confirmClearDetail', "This action is irreversible!"), + cancelId: 1 + }); + + if (response !== 0) { + return; + } + } + await this.saveRecentlyOpened({ workspaces: [], files: [] }); app.clearRecentDocuments(); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 18d160704a6..14430b7c668 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -9,9 +9,9 @@ import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomEmitter } from 'vs/base/browser/event'; import { Color } from 'vs/base/common/color'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, toDisposable, dispose, DisposableStore, setDisposableTracker, DisposableTracker, DisposableInfo } from 'vs/base/common/lifecycle'; -import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $, getActiveDocument } from 'vs/base/browser/dom'; +import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $, getActiveDocument, onDidRegisterWindow, getWindows } from 'vs/base/browser/dom'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; @@ -130,13 +130,34 @@ class ToggleScreencastModeAction extends Action2 { const disposables = new DisposableStore(); - const container = layoutService.container; + const container = layoutService.activeContainer; + const mouseMarker = append(container, $('.screencast-mouse')); disposables.add(toDisposable(() => mouseMarker.remove())); - const onMouseDown = disposables.add(new DomEmitter(container, 'mousedown', true)); - const onMouseUp = disposables.add(new DomEmitter(container, 'mouseup', true)); - const onMouseMove = disposables.add(new DomEmitter(container, 'mousemove', true)); + const keyboardMarker = append(container, $('.screencast-keyboard')); + disposables.add(toDisposable(() => keyboardMarker.remove())); + + const onMouseDown = disposables.add(new Emitter()); + const onMouseUp = disposables.add(new Emitter()); + const onMouseMove = disposables.add(new Emitter()); + + function registerContainerListeners(container: HTMLElement, disposables: DisposableStore): void { + disposables.add(disposables.add(new DomEmitter(container, 'mousedown', true)).event(e => onMouseDown.fire(e))); + disposables.add(disposables.add(new DomEmitter(container, 'mouseup', true)).event(e => onMouseUp.fire(e))); + disposables.add(disposables.add(new DomEmitter(container, 'mousemove', true)).event(e => onMouseMove.fire(e))); + } + + for (const { window, disposables } of getWindows()) { + registerContainerListeners(layoutService.getContainer(window), disposables); + } + + disposables.add(onDidRegisterWindow(({ window, disposables }) => registerContainerListeners(layoutService.getContainer(window), disposables))); + + disposables.add(layoutService.onDidChangeActiveContainer(() => { + layoutService.activeContainer.appendChild(mouseMarker); + layoutService.activeContainer.appendChild(keyboardMarker); + })); const updateMouseIndicatorColor = () => { mouseMarker.style.borderColor = Color.fromHex(configurationService.getValue('screencastMode.mouseIndicatorColor')).toString(); @@ -172,9 +193,6 @@ class ToggleScreencastModeAction extends Action2 { }); })); - const keyboardMarker = append(container, $('.screencast-keyboard')); - disposables.add(toDisposable(() => keyboardMarker.remove())); - const updateKeyboardFontSize = () => { keyboardMarker.style.fontSize = `${clamp(configurationService.getValue('screencastMode.fontSize') || 56, 20, 100)}px`; }; @@ -214,10 +232,23 @@ class ToggleScreencastModeAction extends Action2 { } })); - const onKeyDown = disposables.add(new DomEmitter(window, 'keydown', true)); - const onCompositionStart = disposables.add(new DomEmitter(window, 'compositionstart', true)); - const onCompositionUpdate = disposables.add(new DomEmitter(window, 'compositionupdate', true)); - const onCompositionEnd = disposables.add(new DomEmitter(window, 'compositionend', true)); + const onKeyDown = disposables.add(new Emitter()); + const onCompositionStart = disposables.add(new Emitter()); + const onCompositionUpdate = disposables.add(new Emitter()); + const onCompositionEnd = disposables.add(new Emitter()); + + function registerWindowListeners(window: Window, disposables: DisposableStore): void { + disposables.add(disposables.add(new DomEmitter(window, 'keydown', true)).event(e => onKeyDown.fire(e))); + disposables.add(disposables.add(new DomEmitter(window, 'compositionstart', true)).event(e => onCompositionStart.fire(e))); + disposables.add(disposables.add(new DomEmitter(window, 'compositionupdate', true)).event(e => onCompositionUpdate.fire(e))); + disposables.add(disposables.add(new DomEmitter(window, 'compositionend', true)).event(e => onCompositionEnd.fire(e))); + } + + for (const { window, disposables } of getWindows()) { + registerWindowListeners(window, disposables); + } + + disposables.add(onDidRegisterWindow(({ window, disposables }) => registerWindowListeners(window, disposables))); let length = 0; let composing: Element | undefined = undefined; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 174a0ac7a8c..94284bed195 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, setConstant as setConstantContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext, ProductQualityContext, IsMobileContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, MaximizedEditorGroupContext, TitleBarVisibleContext, TitleBarStyleContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, EmbedderIdentifierContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext, TemporaryWorkspaceContext, ActiveEditorCanToggleReadonlyContext, applyAvailableEditorIds, TitleBarVisibleContext, TitleBarStyleContext, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, onDidRegisterWindow } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -45,7 +45,6 @@ export class WorkbenchContextKeysHandler extends Disposable { private activeEditorGroupLast: IContextKey; private activeEditorGroupLocked: IContextKey; private multipleEditorGroupsContext: IContextKey; - private maximizedEditorGroupContext: IContextKey; private editorsVisibleContext: IContextKey; @@ -139,7 +138,6 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.activeEditorGroupLocked = ActiveEditorGroupLockedContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); - this.maximizedEditorGroupContext = MaximizedEditorGroupContext.bindTo(this.contextKeyService); // Working Copies this.dirtyWorkingCopiesContext = DirtyWorkingCopiesContext.bindTo(this.contextKeyService); @@ -238,8 +236,6 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorGroupService.onDidChangeActiveGroup(() => this.updateEditorGroupContextKeys())); this._register(this.editorGroupService.onDidChangeGroupLocked(() => this.updateEditorGroupContextKeys())); - this._register(this.editorGroupService.onDidChangeGroupMaximized((maximized) => this.maximizedEditorGroupContext.set(maximized))); - this._register(this.editorGroupService.onDidChangeEditorPartOptions(() => this.updateEditorAreaContextKeys())); this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => disposables.add(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(window.document), true)), { window, disposables: this._store })); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 19553fffaa9..ee9061d2700 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -52,7 +52,7 @@ import { IAuxiliaryWindowService, isAuxiliaryWindow } from 'vs/workbench/service //#region Layout Implementation interface ILayoutRuntimeState { - activeContainer: 'main' | number /* window ID */; + activeContainerId: 'main' | number /* window ID */; fullscreen: boolean; maximized: boolean; hasFocus: boolean; @@ -170,7 +170,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi get activeContainer() { return this.getContainerFromDocument(getActiveDocument()); } get containers(): Iterable { const containers: HTMLElement[] = []; - for (const window of getWindows()) { + for (const { window } of getWindows()) { containers.push(this.getContainerFromDocument(window.document)); } @@ -453,8 +453,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private onWindowFocusChanged(hasFocus: boolean): void { if (hasFocus) { const activeContainerId = this.getActiveContainerId(); - if (this.state.runtime.activeContainer !== activeContainerId) { - this.state.runtime.activeContainer = activeContainerId; + if (this.state.runtime.activeContainerId !== activeContainerId) { + this.state.runtime.activeContainerId = activeContainerId; this._onDidChangeActiveContainer.fire(); } } @@ -615,7 +615,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout Runtime State const layoutRuntimeState: ILayoutRuntimeState = { - activeContainer: this.getActiveContainerId(), + activeContainerId: this.getActiveContainerId(), fullscreen: isFullscreen(), hasFocus: this.hostService.hasFocus, maximized: false, @@ -1125,12 +1125,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - getContainer(part: Parts): HTMLElement | undefined { - if (!this.parts.get(part)) { - return undefined; + getContainer(window: Window): HTMLElement; + getContainer(part: Parts): HTMLElement | undefined; + getContainer(windowOrPart: Window | Parts): HTMLElement | undefined { + if (typeof windowOrPart === 'string') { + const part = windowOrPart; + if (!this.parts.get(part)) { + return undefined; + } + + return this.getPart(part).getContainer(); } - return this.getPart(part).getContainer(); + return this.getContainerFromDocument(windowOrPart.document); } isVisible(part: Parts): boolean { @@ -1185,7 +1192,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // with the command center enabled, we should always show - if (this.configurationService.getValue('window.commandCenter')) { + if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER)) { return true; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index b6ee8991056..875a02c676f 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -9,8 +9,8 @@ import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/editor'; import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, - MultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext, MaximizedEditorGroupContext + EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -384,8 +384,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SHOW_EDITORS_IN MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '5_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('togglePreviewMode', "Enable Preview Editors"), toggled: ContextKeyExpr.has('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_MAXIMIZE_EDITOR_GROUP, title: localize('maximizeGroup', "Maximize Group") }, group: '8_group_operations', order: 5, when: ContextKeyExpr.and(MaximizedEditorGroupContext.negate(), MultipleEditorGroupsContext) }); -MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_MAXIMIZE_EDITOR_GROUP, title: localize('unmaximizeGroup', "Unmaximize Group") }, group: '8_group_operations', order: 5, when: MaximizedEditorGroupContext }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_MAXIMIZE_EDITOR_GROUP, title: localize('maximizeGroup', "Maximize Group") }, group: '8_group_operations', order: 5, when: ContextKeyExpr.and(EditorPartMaximizedEditorGroupContext.negate(), EditorPartMultipleEditorGroupsContext) }); +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_MAXIMIZE_EDITOR_GROUP, title: localize('unmaximizeGroup', "Unmaximize Group") }, group: '8_group_operations', order: 5, when: EditorPartMaximizedEditorGroupContext }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_LOCK_GROUP_COMMAND_ID, title: localize('lockGroup', "Lock Group"), toggled: ActiveEditorGroupLockedContext }, group: '8_group_operations', order: 10, when: MultipleEditorGroupsContext }); function appendEditorToolItem(primary: ICommandAction, when: ContextKeyExpression | undefined, order: number, alternative?: ICommandAction, precondition?: ContextKeyExpression | undefined): void { @@ -601,7 +601,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { group: 'z_clear', command: { id: ClearRecentFilesAction.ID, - title: localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened") + title: localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened...") }, order: 1 }); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 889f240d308..34aca940814 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -28,7 +28,9 @@ export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFIN export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { showTabs: 'multiple', highlightModifiedTabs: false, - tabCloseButton: 'right', + tabActionLocation: 'right', + tabActionCloseVisibility: true, + tabActionUnpinVisibility: true, tabSizing: 'fit', tabSizingFixedMinWidth: 50, tabSizingFixedMaxWidth: 160, @@ -104,6 +106,8 @@ function validateEditorPartOptions(options: IEditorPartOptions): void { 'wrapTabs', 'scrollToSwitchTabs', 'highlightModifiedTabs', + 'tabActionCloseVisibility', + 'tabActionUnpinVisibility', 'pinnedTabsOnSeparateRow', 'focusRecentEditorAfterClose', 'showIcons', @@ -138,7 +142,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): void { // String options const stringOptions: Array<[OptionalStringKey, Array]> = [ ['showTabs', ['multiple', 'single', 'none']], - ['tabCloseButton', ['left', 'right', 'off']], + ['tabActionLocation', ['left', 'right']], ['tabSizing', ['fit', 'shrink', 'fixed']], ['pinnedTabSizing', ['normal', 'compact', 'shrink']], ['tabHeight', ['default', 'compact']], diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 1016fcc0e19..846821dd7ca 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -33,7 +33,7 @@ import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, MaximizedEditorGroupContext, MultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; +import { ActiveEditorAvailableEditorIdsContext, ActiveEditorContext, ActiveEditorGroupEmptyContext, EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; import { URI } from 'vs/base/common/uri'; import { getActiveDocument } from 'vs/base/browser/dom'; @@ -1073,7 +1073,7 @@ export class MaximizeGroupHideSidebarAction extends Action2 { title: { value: localize('maximizeEditorHideSidebar', "Maximize Editor Group and Hide Side Bars"), original: 'Maximize Editor Group and Hide Side Bars' }, f1: true, category: Categories.View, - precondition: ContextKeyExpr.and(MaximizedEditorGroupContext.negate(), MultipleEditorGroupsContext) + precondition: ContextKeyExpr.and(EditorPartMaximizedEditorGroupContext.negate(), EditorPartMultipleEditorGroupsContext) }); } @@ -1098,7 +1098,7 @@ export class ToggleMaximizeEditorGroupAction extends Action2 { title: { value: localize('toggleMaximizeEditorGroup', "Toggle Maximize Editor Group"), original: 'Toggle Maximize Editor Group' }, f1: true, category: Categories.View, - precondition: ContextKeyExpr.or(MultipleEditorGroupsContext, MaximizedEditorGroupContext), + precondition: ContextKeyExpr.or(EditorPartMultipleEditorGroupsContext, EditorPartMaximizedEditorGroupContext), keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyM), @@ -1107,16 +1107,16 @@ export class ToggleMaximizeEditorGroupAction extends Action2 { id: MenuId.EditorTitle, order: -10000, // towards the front group: 'navigation', - when: MaximizedEditorGroupContext + when: EditorPartMaximizedEditorGroupContext }, { id: MenuId.EmptyEditorGroup, order: -10000, // towards the front group: 'navigation', - when: MaximizedEditorGroupContext + when: EditorPartMaximizedEditorGroupContext }], icon: Codicon.screenFull, - toggled: MaximizedEditorGroupContext, + toggled: EditorPartMaximizedEditorGroupContext, }); } @@ -1600,7 +1600,7 @@ export class ClearRecentFilesAction extends Action2 { constructor() { super({ id: ClearRecentFilesAction.ID, - title: { value: localize('clearRecentFiles', "Clear Recently Opened"), original: 'Clear Recently Opened' }, + title: { value: localize('clearRecentFiles', "Clear Recently Opened..."), original: 'Clear Recently Opened...' }, f1: true, category: Categories.File }); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts index 6a6fca85b64..3c4ac7d594b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupWatermark.ts @@ -28,8 +28,7 @@ const openFileNonMacOnly: WatermarkEntry = { text: localize('watermark.openFile' const openFolderNonMacOnly: WatermarkEntry = { text: localize('watermark.openFolder', "Open Folder"), id: 'workbench.action.files.openFolder', mac: false }; const openFileOrFolderMacOnly: WatermarkEntry = { text: localize('watermark.openFileFolder', "Open File or Folder"), id: 'workbench.action.files.openFileFolder', mac: true }; const openRecent: WatermarkEntry = { text: localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; -const newUntitledFile: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile' }; -const newUntitledFileMacOnly: WatermarkEntry = Object.assign({ mac: true }, newUntitledFile); +const newUntitledFileMacOnly: WatermarkEntry = { text: localize('watermark.newUntitledFile', "New Untitled Text File"), id: 'workbench.action.files.newUntitledFile', mac: true }; const findInFiles: WatermarkEntry = { text: localize('watermark.findInFiles', "Find in Files"), id: 'workbench.action.findInFiles' }; const toggleTerminal: WatermarkEntry = { text: localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: 'workbench.action.terminal.toggleTerminal', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; const startDebugging: WatermarkEntry = { text: localize('watermark.startDebugging', "Start Debugging"), id: 'workbench.action.debug.start', when: ContextKeyExpr.equals('terminalProcessSupported', true) }; diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 6cffd8296b4..1a9679abf56 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -32,6 +32,9 @@ import { findGroup } from 'vs/workbench/services/editor/common/editorGroupFinder import { SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { EditorPartMaximizedEditorGroupContext, EditorPartMultipleEditorGroupsContext } from 'vs/workbench/common/contextkeys'; interface IEditorPartUIState { readonly serializedGrid: ISerializedGrid; @@ -140,6 +143,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { private container: HTMLElement | undefined; + private scopedInstantiationService!: IInstantiationService; + private centeredLayoutWidget!: CenteredViewLayout; private gridWidget!: SerializableGrid; @@ -156,7 +161,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(id, { hasTitle: false }, themeService, storageService, layoutService); @@ -214,7 +220,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { readonly sideGroup: IEditorSideGroup = { openEditor: (editor, options) => { - const [group] = this.instantiationService.invokeFunction(accessor => findGroup(accessor, { editor, options }, SIDE_GROUP)); + const [group] = this.scopedInstantiationService.invokeFunction(accessor => findGroup(accessor, { editor, options }, SIDE_GROUP)); return group.openEditor(editor, options); } @@ -624,11 +630,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Create group view let groupView: IEditorGroupView; if (from instanceof EditorGroupView) { - groupView = EditorGroupView.createCopy(from, this.editorPartsView, this, this.groupsLabel, this.count, this.instantiationService); + groupView = EditorGroupView.createCopy(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService,); } else if (isSerializedEditorGroupModel(from)) { - groupView = EditorGroupView.createFromSerialized(from, this.editorPartsView, this, this.groupsLabel, this.count, this.instantiationService); + groupView = EditorGroupView.createFromSerialized(from, this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService); } else { - groupView = EditorGroupView.createNew(this.editorPartsView, this, this.groupsLabel, this.count, this.instantiationService); + groupView = EditorGroupView.createNew(this.editorPartsView, this, this.groupsLabel, this.count, this.scopedInstantiationService); } // Keep in map @@ -930,7 +936,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { createEditorDropTarget(container: unknown, delegate: IEditorDropTargetDelegate): IDisposable { assertType(container instanceof HTMLElement); - return this.instantiationService.createInstance(EditorDropTarget, container, delegate); + return this.scopedInstantiationService.createInstance(EditorDropTarget, container, delegate); } //#region Part @@ -967,6 +973,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { this.container.classList.add('content'); parent.appendChild(this.container); + // Scoped instantiation service + const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.container)); + this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( + [IContextKeyService, scopedContextKeyService] + )); + // Grid control this.doCreateGridControl(options); @@ -977,6 +989,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Drag & Drop support this.setupDragAndDropSupport(parent, this.container); + // Context keys + this.handleContextKeys(scopedContextKeyService); + // Signal ready this.whenReadyPromise.complete(); this._isReady = true; @@ -989,6 +1004,32 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return this.container; } + private handleContextKeys(contextKeyService: IContextKeyService): void { + const multipleEditorGroupsContext = EditorPartMultipleEditorGroupsContext.bindTo(contextKeyService); + const maximizedEditorGroupContext = EditorPartMaximizedEditorGroupContext.bindTo(contextKeyService); + + const updateContextKeys = () => { + const groupCount = this.count; + if (groupCount > 1) { + multipleEditorGroupsContext.set(true); + } else { + multipleEditorGroupsContext.reset(); + } + + if (this.hasMaximizedGroup()) { + maximizedEditorGroupContext.set(true); + } else { + maximizedEditorGroupContext.reset(); + } + }; + + updateContextKeys(); + + this._register(this.onDidAddGroup(() => updateContextKeys())); + this._register(this.onDidRemoveGroup(() => updateContextKeys())); + this._register(this.onDidChangeGroupMaximized(() => updateContextKeys())); + } + private setupDragAndDropSupport(parent: HTMLElement, container: HTMLElement): void { // Editor drop target @@ -1335,9 +1376,10 @@ export class MainEditorPart extends EditorPart { @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(editorPartsView, Parts.EDITOR_PART, '', false, instantiationService, themeService, configurationService, storageService, layoutService, hostService); + super(editorPartsView, Parts.EDITOR_PART, '', false, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); } } @@ -1356,10 +1398,11 @@ export class AuxiliaryEditorPart extends EditorPart implements IAuxiliaryEditorP @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IContextKeyService contextKeyService: IContextKeyService ) { const id = AuxiliaryEditorPart.COUNTER++; - super(editorPartsView, `workbench.parts.auxiliaryEditor.${id}`, groupsLabel, true, instantiationService, themeService, configurationService, storageService, layoutService, hostService); + super(editorPartsView, `workbench.parts.auxiliaryEditor.${id}`, groupsLabel, true, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); } protected override saveState(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 4a1b22ae99f..d019690429b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -315,7 +315,6 @@ const nlsMultiSelection = localize('multiSelection', "{0} selections"); const nlsEOLLF = localize('endOfLineLineFeed', "LF"); const nlsEOLCRLF = localize('endOfLineCarriageReturnLineFeed', "CRLF"); - export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly tabFocusModeElement = this._register(new MutableDisposable()); @@ -335,6 +334,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private toRender: StateChange | undefined = undefined; private editorService: IEditorService; + private targetWindow = window; constructor( @IEditorService editorService: IEditorService, @@ -570,7 +570,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (toRender) { this.doRenderNow(); } - }); + }, this.targetWindow); } else { this.toRender.combine(changed); } diff --git a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts index 6330c3e814a..3f14abf5b9e 100644 --- a/src/vs/workbench/browser/parts/editor/editorTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTitleControl.ts @@ -77,7 +77,7 @@ export class EditorTitleControl extends Themable { } private createBreadcrumbsControl(): BreadcrumbsControlFactory | undefined { - if (this.groupsView.partOptions.showTabs !== 'multiple') { + if (this.groupsView.partOptions.showTabs === 'single') { return undefined; // Single tabs have breadcrumbs inlined. No tabs have no breadcrumbs. } @@ -175,7 +175,7 @@ export class EditorTitleControl extends Themable { // Update editor tabs control if options changed if ( oldOptions.showTabs !== newOptions.showTabs || - (newOptions.showTabs === 'multiple' && oldOptions.pinnedTabsOnSeparateRow !== newOptions.pinnedTabsOnSeparateRow) + (newOptions.showTabs !== 'single' && oldOptions.pinnedTabsOnSeparateRow !== newOptions.pinnedTabsOnSeparateRow) ) { // Clear old this.editorTabsControlDisposable.clear(); diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 5fde102f436..3c892652a73 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -115,9 +115,9 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-right, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.tab-actions-off:not(.sticky-compact), +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon.close-action-off:not(.sticky-compact), .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.has-icon.tab-actions-right, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.has-icon.tab-actions-off:not(.sticky-compact) { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.has-icon.close-action-off:not(.sticky-compact) { padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab actions is not left (unless sticky-compact) */ } @@ -205,9 +205,9 @@ } .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-left::after, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off::after, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-action-off::after, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.tab-actions-left::after, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.tab-actions-off::after { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.close-action-off::after { content: ''; display: flex; flex: 0; @@ -310,7 +310,7 @@ padding-right: 5px; /* with tab sizing shrink/fixed and badges, we want a right-padding because the close button is hidden */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.tab-actions-left):not(.tab-actions-off) .tab-label { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.tab-actions-left):not(.close-action-off) .tab-label { padding-right: 5px; /* ensure that the gradient does not show when tab actions show https://github.com/microsoft/vscode/issues/189625*/ } @@ -365,16 +365,16 @@ overflow: visible; /* ...but still show the tab actions on hover, focus and when dirty or sticky */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off:not(.dirty):not(.sticky) > .tab-actions, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.sticky-compact > .tab-actions { - display: none; /* hide the tab actions when we are configured to hide it (unless dirty or sticky, but always when sticky-compact) */ +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off:not(.dirty) > .tab-actions, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off.sticky-compact > .tab-actions { + display: none; /* hide the tab actions when we are configured to hide it (unless dirty, but always when sticky-compact) */ } .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-actions .action-label, /* always show tab actions for active tab */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-actions .action-label:focus, /* always show tab actions on focus */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-actions .action-label, /* always show tab actions on hover */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, /* always show tab actions on hover */ -.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky > .tab-actions .action-label, /* always show tab actions for sticky tabs */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, /* always show tab actions for sticky tabs */ .monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-actions .action-label { /* always show tab actions for dirty tabs */ opacity: 1; } @@ -404,7 +404,7 @@ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-actions .action-label, -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky > .tab-actions .action-label, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sticky:not(.pinned-action-off) > .tab-actions .action-label, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-actions .action-label { opacity: 0.5; /* show tab actions dimmed for inactive group */ } @@ -415,25 +415,24 @@ /* Tab Actions: Off */ -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off { padding-right: 10px; /* give a little bit more room if tab actions is off */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.tab-actions-off:not(.sticky-compact), -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.tab-actions-off:not(.sticky-compact) { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-action-off:not(.sticky-compact), +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed.close-action-off:not(.sticky-compact) { padding-right: 5px; /* we need less room when sizing is shrink/fixed (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty-border-top > .tab-actions { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off.dirty-border-top > .tab-actions { display: none; /* hide dirty state when highlightModifiedTabs is enabled and when running without tab actions */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.dirty:not(.dirty-border-top):not(.sticky-compact), -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off.sticky:not(.sticky-compact) { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off.dirty:not(.dirty-border-top):not(.sticky-compact) { padding-right: 0; /* remove extra padding when we are running without tab actions (unless tab is sticky-compact) */ } -.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.tab-actions-off > .tab-actions { +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-action-off > .tab-actions { pointer-events: none; /* don't allow tab actions to be clicked when running without tab actions */ } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 53dde60d05c..8732bf95f7c 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -745,7 +745,9 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Redraw tabs when other options change if ( oldOptions.labelFormat !== newOptions.labelFormat || - oldOptions.tabCloseButton !== newOptions.tabCloseButton || + oldOptions.tabActionLocation !== newOptions.tabActionLocation || + oldOptions.tabActionCloseVisibility !== newOptions.tabActionCloseVisibility || + oldOptions.tabActionUnpinVisibility !== newOptions.tabActionUnpinVisibility || oldOptions.tabSizing !== newOptions.tabSizing || oldOptions.pinnedTabSizing !== newOptions.pinnedTabSizing || oldOptions.showIcons !== newOptions.showIcons || @@ -1036,7 +1038,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Fixes https://github.com/microsoft/vscode/issues/18733 tab.classList.add('dragged'); - scheduleAtNextAnimationFrame(() => tab.classList.remove('dragged')); + scheduleAtNextAnimationFrame(() => tab.classList.remove('dragged'), getWindow(tab)); })); // Drop support @@ -1321,19 +1323,33 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.redrawTabLabel(editor, tabIndex, tabContainer, tabLabelWidget, tabLabel); // Action - const tabAction = isTabSticky ? this.unpinEditorAction : this.closeEditorAction; - if (!tabActionBar.hasAction(tabAction)) { + const hasUnpinAction = isTabSticky && options.tabActionUnpinVisibility; + const hasCloseAction = !hasUnpinAction && options.tabActionCloseVisibility; + const hasAction = hasUnpinAction || hasCloseAction; + + // If no action is visible, ensure to clear it + if (!hasAction) { if (!tabActionBar.isEmpty()) { tabActionBar.clear(); } + } + // If action is visible, ensure it is showing + else { + const tabAction = hasUnpinAction ? this.unpinEditorAction : this.closeEditorAction; + if (!tabActionBar.hasAction(tabAction)) { + if (!tabActionBar.isEmpty()) { + tabActionBar.clear(); + } - tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); + tabActionBar.push(tabAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(tabAction) }); + } } - // Settings - const tabActionsVisibility = isTabSticky && options.pinnedTabSizing === 'compact' ? 'off' /* treat sticky compact tabs as tabCloseButton: 'off' */ : options.tabCloseButton; - for (const option of ['off', 'left', 'right']) { - tabContainer.classList.toggle(`tab-actions-${option}`, tabActionsVisibility === option); + tabContainer.classList.toggle(`pinned-action-off`, isTabSticky && !hasUnpinAction); + tabContainer.classList.toggle(`close-action-off`, !hasUnpinAction && !hasCloseAction); + + for (const option of ['left', 'right']) { + tabContainer.classList.toggle(`tab-actions-${option}`, hasAction && options.tabActionLocation === option); } const tabSizing = isTabSticky && options.pinnedTabSizing === 'shrink' ? 'shrink' /* treat sticky shrink tabs as tabSizing: 'shrink' */ : options.tabSizing; @@ -1593,16 +1609,25 @@ export class MultiEditorTabsControl extends EditorTabsControl { Object.assign(this.dimensions, dimensions); 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 - // when we have restored or when idle otherwise. if (!this.layoutScheduler.value) { - const scheduledLayout = (this.lifecycleService.phase >= LifecyclePhase.Restored ? scheduleAtNextAnimationFrame : runWhenIdle)(() => { + + // 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 + // when we have restored or when idle otherwise. + + const layoutFunction = () => { this.doLayout(this.dimensions, this.layoutScheduler.value?.options /* ensure to pick up latest options */); this.layoutScheduler.clear(); - }); + }; + + let scheduledLayout: IDisposable; + if (this.lifecycleService.phase >= LifecyclePhase.Restored) { + scheduledLayout = scheduleAtNextAnimationFrame(layoutFunction, getWindow(this.tabsContainer)); + } else { + scheduledLayout = runWhenIdle(layoutFunction); + } this.layoutScheduler.value = { options, dispose: () => scheduledLayout.dispose() }; } diff --git a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts index d1c0cf8f727..baf08bb4504 100644 --- a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts @@ -11,6 +11,7 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { IToolbarActions } from 'vs/workbench/common/editor'; export class NoEditorTabsControl extends EditorTabsControl { + private activeEditor: EditorInput | null = null; protected prepareEditorActions(editorActions: IToolbarActions): IToolbarActions { return { @@ -20,10 +21,27 @@ export class NoEditorTabsControl extends EditorTabsControl { } openEditor(editor: EditorInput): boolean { - return false; + return this.handleOpenedEditors(); } openEditors(editors: EditorInput[]): boolean { + return this.handleOpenedEditors(); + } + + private handleOpenedEditors(): boolean { + const didChange = this.activeEditorChanged(); + this.activeEditor = this.tabsModel.activeEditor; + return didChange; + } + + private activeEditorChanged(): boolean { + if ( + !this.activeEditor && this.tabsModel.activeEditor || // active editor changed from null => editor + this.activeEditor && !this.tabsModel.activeEditor || // active editor changed from editor => null + (!this.activeEditor || !this.tabsModel.isActive(this.activeEditor)) // active editor changed from editorA => editorB + ) { + return true; + } return false; } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index c3f0490bedd..fb0f7f5d318 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/notificationsToasts'; import { localize } from 'vs/nls'; import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemContentChangeKind } from 'vs/workbench/common/notifications'; import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { addDisposableListener, EventType, Dimension, scheduleAtNextAnimationFrame, isAncestorOfActiveElement } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, Dimension, scheduleAtNextAnimationFrame, isAncestorOfActiveElement, getWindow } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event, Emitter } from 'vs/base/common/event'; @@ -150,7 +150,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast // (see also https://github.com/microsoft/vscode/issues/107935) const itemDisposables = new DisposableStore(); this.mapNotificationToDisposable.set(item, itemDisposables); - itemDisposables.add(scheduleAtNextAnimationFrame(() => this.doAddToast(item, itemDisposables))); + itemDisposables.add(scheduleAtNextAnimationFrame(() => this.doAddToast(item, itemDisposables), getWindow(this.container))); } private doAddToast(item: INotificationViewItem, itemDisposables: DisposableStore): void { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index fc428b3c8ce..2fe0596b186 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -24,7 +24,7 @@ import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menuba import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Parts, IWorkbenchLayoutService, LayoutSettings, ActivityBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; +import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -54,8 +54,6 @@ import { IEditorCommandsContext, IToolbarActions } from 'vs/workbench/common/edi export class TitlebarPart extends Part implements ITitleService { - private static readonly configCommandCenter = 'window.commandCenter'; - declare readonly _serviceBrand: undefined; //#region IView @@ -159,7 +157,7 @@ export class TitlebarPart extends Part implements ITitleService { } get isCommandCenterVisible() { - return this.configurationService.getValue(TitlebarPart.configCommandCenter); + return this.configurationService.getValue(LayoutSettings.COMMAND_CENTER); } private registerListeners(): void { @@ -202,7 +200,7 @@ export class TitlebarPart extends Part implements ITitleService { } } - if (event.affectsConfiguration(TitlebarPart.configCommandCenter)) { + if (event.affectsConfiguration(LayoutSettings.COMMAND_CENTER)) { this.updateTitle(); this._onDidChangeCommandCenterVisibility.fire(); this._onDidChange.fire(undefined); @@ -640,7 +638,7 @@ class ToogleConfigAction extends Action2 { registerAction2(class ToogleCommandCenter extends ToogleConfigAction { constructor() { - super('window.commandCenter', localize('toggle.commandCenter', 'Command Center'), 1); + super(LayoutSettings.COMMAND_CENTER, localize('toggle.commandCenter', 'Command Center'), 1); } }); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b65a42ef2fa..1c16044a043 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -11,7 +11,7 @@ import { ConfigurationMigrationWorkbenchContribution, DynamicWorkbenchConfigurat import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { ActivityBarPosition, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -38,9 +38,9 @@ const registry = Registry.as(ConfigurationExtensions.Con description: localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), default: 'default', }, - 'workbench.editor.showTabs': { + [LayoutSettings.EDITOR_TABS_MODE]: { 'type': 'string', - 'enum': ['multiple', 'single', 'none'], + 'enum': [EditorTabsMode.MULTIPLE, EditorTabsMode.SINGLE, EditorTabsMode.NONE], 'enumDescriptions': [ localize('workbench.editor.showTabs.multiple', "Each editor is displayed as a tab in the editor title area."), localize('workbench.editor.showTabs.single', "The active editor is displayed as a single large tab in the editor title area."), @@ -153,11 +153,21 @@ const registry = Registry.as(ConfigurationExtensions.Con } } }, - 'workbench.editor.tabCloseButton': { - 'type': 'string', - 'enum': ['left', 'right', 'off'], - 'default': 'right', - 'markdownDescription': localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") + 'workbench.editor.tabActionLocation': { + type: 'string', + enum: ['left', 'right'], + default: 'right', + markdownDescription: localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabActionLocation' }, "Controls the position of the editor's tabs action buttons (close, unpin). This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`.") + }, + 'workbench.editor.tabActionCloseVisibility': { + type: 'boolean', + default: true, + description: localize('workbench.editor.tabActionCloseVisibility', "Controls the visibility of the tab close action button.") + }, + 'workbench.editor.tabActionUnpinVisibility': { + type: 'boolean', + default: true, + description: localize('workbench.editor.tabActionUnpinVisibility', "Controls the visibility of the tab unpin action button.") }, 'workbench.editor.tabSizing': { 'type': 'string', @@ -632,7 +642,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': isMacintosh ? ' \u2014 ' : ' - ', 'markdownDescription': localize("window.titleSeparator", "Separator used by {0}.", '`#window.title#`') }, - 'window.commandCenter': { + [LayoutSettings.COMMAND_CENTER]: { type: 'boolean', default: true, markdownDescription: isWeb ? @@ -806,11 +816,21 @@ Registry.as(Extensions.ConfigurationMigration) return [['workbench.editor.doubleClickTabToToggleEditorGroupSizes', { value: value }]]; } }, { - key: 'workbench.editor.showTabs', migrateFn: (value: any) => { + key: LayoutSettings.EDITOR_TABS_MODE, migrateFn: (value: any) => { if (typeof value === 'boolean') { - value = value ? 'multiple' : 'single'; + value = value ? EditorTabsMode.MULTIPLE : EditorTabsMode.SINGLE; } - return [['workbench.editor.showTabs', { value: value }]]; + return [[LayoutSettings.EDITOR_TABS_MODE, { value }]]; + } + }, { + key: 'workbench.editor.tabCloseButton', migrateFn: (value: any) => { + const result: ConfigurationKeyValuePairs = []; + if (value === 'left' || value === 'right') { + result.push(['workbench.editor.tabActionLocation', { value }]); + } else if (value === 'off') { + result.push(['workbench.editor.tabActionCloseVisibility', { value: false }]); + } + return result; } }, { key: 'zenMode.hideTabs', migrateFn: (value: any) => { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts index d58629b85f7..d6844289a43 100644 --- a/src/vs/workbench/common/contextkeys.ts +++ b/src/vs/workbench/common/contextkeys.ts @@ -71,6 +71,11 @@ export const ActiveEditorGroupLockedContext = new RawContextKey('active export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false, localize('multipleEditorGroups', "Whether there are multiple editor groups opened")); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); +// Editor Part Context Keys +export const EditorPartMultipleEditorGroupsContext = new RawContextKey('editorPartMultipleEditorGroups', false, localize('editorPartMultipleEditorGroups', "Whether there are multiple editor groups opened in an editor part")); +export const EditorPartSingleEditorGroupsContext = EditorPartMultipleEditorGroupsContext.toNegated(); +export const EditorPartMaximizedEditorGroupContext = new RawContextKey('editorPartMaximizedEditorGroup', false, localize('editorPartEditorGroupMaximized', "Editor Part has a maximized group")); + // Editor Layout Context Keys export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false, localize('editorIsOpen', "Whether an editor is open")); export const InEditorZenModeContext = new RawContextKey('inZenMode', false, localize('inZenMode', "Whether Zen mode is enabled")); @@ -79,7 +84,6 @@ export const SplitEditorsVertically = new RawContextKey('splitEditorsVe export const EditorAreaVisibleContext = new RawContextKey('editorAreaVisible', true, localize('editorAreaVisible', "Whether the editor area is visible")); export const EditorTabsVisibleContext = new RawContextKey('editorTabsVisible', true, localize('editorTabsVisible', "Whether editor tabs are visible")); export const EditorPinnedAndUnpinnedTabsContext = new RawContextKey('editorPinnedAndUnpinnedTabsVisible', false, true); -export const MaximizedEditorGroupContext = new RawContextKey('maximizedEditorGroup', false, localize('editorGroupMaximized', "Editor group is maximized")); //#endregion diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 16ce2350f15..74dcc0740f6 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1113,7 +1113,9 @@ interface IEditorPartConfiguration { wrapTabs?: boolean; scrollToSwitchTabs?: boolean; highlightModifiedTabs?: boolean; - tabCloseButton?: 'left' | 'right' | 'off'; + tabActionLocation?: 'left' | 'right'; + tabActionCloseVisibility?: boolean; + tabActionUnpinVisibility?: boolean; tabSizing?: 'fit' | 'shrink' | 'fixed'; tabSizingFixedMinWidth?: number; tabSizingFixedMaxWidth?: number; diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 82c067d6b1a..c7f1c97fd14 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -458,7 +458,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { disposable.dispose(); this._onDidChangeItemHeight.fire({ element, height: newHeight }); - })); + }, dom.getWindow(templateData.value))); } } @@ -496,7 +496,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { disposable.dispose(); this._onDidChangeItemHeight.fire({ element, height: newHeight }); - })); + }, dom.getWindow(templateData.value))); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatQuick.ts b/src/vs/workbench/contrib/chat/browser/chatQuick.ts index 170c2ae89e6..c4e06196df2 100644 --- a/src/vs/workbench/contrib/chat/browser/chatQuick.ts +++ b/src/vs/workbench/contrib/chat/browser/chatQuick.ts @@ -74,6 +74,12 @@ export class QuickChatService extends Disposable implements IQuickChatService { open(providerId?: string, options?: IQuickChatOpenOptions): void { if (this._input) { + if (this._currentChat && options?.query) { + this._currentChat.setValue(options.query, options.selection); + if (!options.isPartialQuery) { + this._currentChat.acceptInput(); + } + } return this.focus(); } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d7526a49c87..c6f79e430df 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -407,7 +407,7 @@ export class ChatWidget extends Disposable implements IChatWidget { dom.scheduleAtNextAnimationFrame(() => { // Can't set scrollTop during this event listener, the list might overwrite the change revealLastElement(this.tree); - }, 0); + }, dom.getWindow(this.listContainer), 0); } } @@ -640,7 +640,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const inputPartHeight = this.inputPart.layout(possibleMaxHeight, width); const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight); this.layout(newHeight + inputPartHeight, width); - }); + }, dom.getWindow(this.listContainer)); })); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 7334337b118..6db8aa47a21 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -89,7 +89,7 @@ class InputEditorDecorations extends Disposable { private registerViewModelListeners(): void { this.viewModelDisposables.value = this.widget.viewModel?.onDidChange(e => { - if (e?.kind === 'changePlaceholder') { + if (e?.kind === 'changePlaceholder' || e?.kind === 'initialize') { this.updateInputEditorDecorations(); } }); @@ -127,7 +127,6 @@ 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 viewModel = this.widget.viewModel; if (!viewModel) { @@ -136,10 +135,7 @@ class InputEditorDecorations extends Disposable { if (!inputValue) { const viewModelPlaceholder = this.widget.viewModel?.inputPlaceholder; - const defaultPlaceholder = slashCommands?.length ? - localize('interactive.input.placeholderWithCommands', "Ask a question or type '@' or '/'") : - localize('interactive.input.placeholderNoCommands', "Ask a question"); - const placeholder = viewModelPlaceholder ?? defaultPlaceholder; + const placeholder = viewModelPlaceholder ?? ''; const decoration: IDecorationOptions[] = [ { range: { diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 2d524d8172d..057ad815146 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -109,7 +109,7 @@ } .interactive-item-container .header .avatar .codicon { - color: var(--vscode-chat-avatarForeground); + color: var(--vscode-chat-avatarForeground) !important; font-size: 14px; } diff --git a/src/vs/workbench/contrib/chat/common/chatColors.ts b/src/vs/workbench/contrib/chat/common/chatColors.ts index c5a226fa28d..97c8bc85f5a 100644 --- a/src/vs/workbench/contrib/chat/common/chatColors.ts +++ b/src/vs/workbench/contrib/chat/common/chatColors.ts @@ -15,13 +15,13 @@ export const chatRequestBorder = registerColor( export const chatSlashCommandBackground = registerColor( 'chat.slashCommandBackground', - { dark: badgeBackground, light: badgeBackground, hcDark: Color.white, hcLight: badgeBackground }, + { dark: '#34414B', light: '#D2ECFF', hcDark: Color.white, hcLight: badgeBackground }, localize('chat.slashCommandBackground', 'The background color of a chat slash command.') ); export const chatSlashCommandForeground = registerColor( 'chat.slashCommandForeground', - { dark: badgeForeground, light: badgeForeground, hcDark: Color.black, hcLight: badgeForeground }, + { dark: '#40A6FF', light: '#306CA2', hcDark: Color.black, hcLight: badgeForeground }, localize('chat.slashCommandForeground', 'The foreground color of a chat slash command.') ); diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index dcaf0343508..d6337756df3 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -26,7 +26,7 @@ export function isWelcomeVM(item: unknown): item is IChatWelcomeMessageViewModel return !!item && typeof item === 'object' && 'content' in item; } -export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | null; +export type IChatViewModelChangeEvent = IChatAddRequestEvent | IChangePlaceholderEvent | IChatSessionInitEvent | null; export interface IChatAddRequestEvent { kind: 'addRequest'; @@ -36,6 +36,10 @@ export interface IChangePlaceholderEvent { kind: 'changePlaceholder'; } +export interface IChatSessionInitEvent { + kind: 'initialize'; +} + export interface IChatViewModel { readonly initState: ChatModelInitState; readonly providerId: string; @@ -184,7 +188,10 @@ export class ChatViewModel extends Disposable implements IChatViewModel { } } - this._onDidChange.fire(e.kind === 'addRequest' ? { kind: 'addRequest' } : null); + const modelEventToVmEvent: IChatViewModelChangeEvent = e.kind === 'addRequest' ? { kind: 'addRequest' } : + e.kind === 'initialize' ? { kind: 'initialize' } : + null; + this._onDidChange.fire(modelEventToVmEvent); })); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 2659700d67d..98342adaf6d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -27,7 +27,7 @@ import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/b import { registerColor } from 'vs/platform/theme/common/colorRegistry'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); -const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find (\u21C5 for history)"); +const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match"); const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match"); const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index d015ba6761b..65ad313fff7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -170,7 +170,7 @@ export class CommentNode extends Disposable { this._scrollable = new Scrollable({ forceIntegerValues: true, smoothScrollDuration: 125, - scheduleAtNextAnimationFrame: cb => dom.scheduleAtNextAnimationFrame(cb) + scheduleAtNextAnimationFrame: cb => dom.scheduleAtNextAnimationFrame(cb, dom.getWindow(container)) }); this._scrollableElement = this._register(new SmoothScrollableElement(body, { horizontal: ScrollbarVisibility.Visible, @@ -489,7 +489,7 @@ export class CommentNode extends Disposable { dom.scheduleAtNextAnimationFrame(() => { this._commentEditor!.layout({ width: container.clientWidth - 14, height: this._editorHeight }); this._commentEditor!.focus(); - }); + }, dom.getWindow(editContainer)); const lastLine = this._commentEditorModel.getLineCount(); const lastColumn = this._commentEditorModel.getLineLength(lastLine) + 1; diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 2eb6c4c38bb..4e7ca0f1050 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -869,8 +869,20 @@ export class CommentController implements IEditorContribution { const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === thread.owner && Range.lift(zoneWidget.commentThread.range)?.equalsRange(thread.range)); if (thread.isReply && matchedZones.length) { this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: true }); - const matchedZone = matchedZones[0]; - matchedZone.setPendingComment(thread.body); + matchedZones[0].setPendingComment(thread.body); + } else if (matchedZones.length) { + this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: false }); + const existingPendingComment = matchedZones[0].getPendingComments().newComment; + // We need to try to reconcile the existing pending comment with the incoming pending comment + let pendingComment: string; + if (!existingPendingComment || thread.body.includes(existingPendingComment)) { + pendingComment = thread.body; + } else if (existingPendingComment.includes(thread.body)) { + pendingComment = existingPendingComment; + } else { + pendingComment = `${existingPendingComment}\n${thread.body}`; + } + matchedZones[0].setPendingComment(pendingComment); } else if (!thread.isReply) { const threadStillAvailable = this.commentService.removeContinueOnComment({ owner: thread.owner, uri: editorURI, range: thread.range, isReply: false }); if (!threadStillAvailable) { diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 83372ce9637..78536836e73 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -36,7 +36,7 @@ import { debugToolBarBackground, debugToolBarBorder } from 'vs/workbench/contrib import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_IN_DEBUG_MODE, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Codicon } from 'vs/base/common/codicons'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; @@ -132,6 +132,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { if (e.affectsConfiguration('debug.toolBarLocation')) { this.updateScheduler.schedule(); } + if (e.affectsConfiguration(LayoutSettings.EDITOR_TABS_MODE) || e.affectsConfiguration(LayoutSettings.COMMAND_CENTER)) { + this._yRange = undefined; + this.setYCoordinate(); + } })); this._register(this.debugToolBarMenu.onDidChange(() => this.updateScheduler.schedule())); this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => { @@ -163,7 +167,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 - this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - (this.layoutService.mainContainerOffset.top)); + this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - 14); }); const mouseUpListener = dom.addDisposableGenericMouseUpListener(window, (e: MouseEvent) => { @@ -185,6 +189,11 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const position = parseFloat(left) / window.innerWidth; this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.PROFILE, StorageTarget.MACHINE); } + if (this.yCoordinate) { + this.storageService.store(DEBUG_TOOLBAR_Y_KEY, this.yCoordinate, StorageScope.PROFILE, StorageTarget.MACHINE); + } else { + this.storageService.remove(DEBUG_TOOLBAR_Y_KEY, StorageScope.PROFILE); + } } override updateStyles(): void { @@ -208,12 +217,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { } } - private setYCoordinate(y = this.yCoordinate): void { - const titlebarOffset = this.layoutService.mainContainerOffset.top; - this.$el.style.top = `${titlebarOffset + y}px`; - this.yCoordinate = y; - } - private setCoordinates(x?: number, y?: number): void { if (!this.isVisible) { return; @@ -230,12 +233,38 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { if (y === undefined) { y = this.storageService.getNumber(DEBUG_TOOLBAR_Y_KEY, StorageScope.PROFILE, 0); } - const titleAreaHeight = 35; - if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) { - const moveToTop = y < titleAreaHeight; - this.setYCoordinate(moveToTop ? 0 : titleAreaHeight); - this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.PROFILE, StorageTarget.MACHINE); + + this.setYCoordinate(y); + } + + private setYCoordinate(y = this.yCoordinate): void { + const [yMin, yMax] = this.yRange; + y = Math.max(yMin, Math.min(y, yMax)); + this.$el.style.top = `${y}px`; + this.yCoordinate = y; + } + + private _yRange: [number, number] | undefined; + private get yRange(): [number, number] { + if (!this._yRange) { + const isTitleBarVisible = this.layoutService.isVisible(Parts.TITLEBAR_PART); + const yMin = isTitleBarVisible ? 0 : this.layoutService.mainContainerOffset.top; + let yMax = 0; + + if (isTitleBarVisible) { + if (this.configurationService.getValue(LayoutSettings.COMMAND_CENTER) === true) { + yMax += 35; + } else { + yMax += 28; + } + } + + if (this.configurationService.getValue(LayoutSettings.EDITOR_TABS_MODE) !== EditorTabsMode.NONE) { + yMax += 35; + } + this._yRange = [yMin, yMax]; } + return this._yRange; } private show(): void { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index f32eaab62f8..fbe36b38c1a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -5,11 +5,12 @@ .monaco-workbench .debug-toolbar { position: absolute; - z-index: 39; - height: 32px; + z-index: 3000; + height: 26px; display: flex; padding-left: 7px; border-radius: 4px; + -webkit-app-region: no-drag; } .monaco-workbench .debug-toolbar .monaco-action-bar .action-item { diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index 534443553ba..ecc3caa3815 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -95,7 +95,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot @INotebookService private readonly _notebookService: INotebookService, @IFileDialogService private readonly _fileDialogService: IFileDialogService ) { - const input = NotebookEditorInput.create(instantiationService, resource, 'interactive', {}); + const input = NotebookEditorInput.getOrCreate(instantiationService, resource, undefined, 'interactive', {}); super(); this._notebookEditorInput = input; this._register(this._notebookEditorInput); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index f405b53b5e0..92327e752c6 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -998,7 +998,7 @@ export class DeletedElement extends SingleSideDiffElement { } this.layoutNotebookCell(); - }); + }, DOM.getWindow(this._diffEditorContainer)); } _buildOutputRendererContainer() { @@ -1201,7 +1201,7 @@ export class InsertElement extends SingleSideDiffElement { if (this._diagonalFill) { this._diagonalFill.style.height = `${this.cell.layoutInfo.editorHeight + this.cell.layoutInfo.editorMargin + this.cell.layoutInfo.metadataStatusHeight + this.cell.layoutInfo.metadataHeight + this.cell.layoutInfo.outputTotalHeight + this.cell.layoutInfo.outputStatusHeight}px`; } - }); + }, DOM.getWindow(this._diffEditorContainer)); } override dispose() { @@ -1626,7 +1626,7 @@ export class ModifiedElement extends AbstractElementRenderer { } this.layoutNotebookCell(); - }); + }, DOM.getWindow(this._diffEditorContainer)); } override dispose() { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 2d6eb9ea124..35c1e3cf692 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -463,7 +463,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return diffElement.original; }, DiffSide.Original); } - }); + }, DOM.getWindow(this._listViewContainer)); }; this._localStore.add(this._list.onDidChangeContentHeight(() => { @@ -763,7 +763,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD DOM.scheduleAtNextAnimationFrame(() => { webview?.ackHeight([{ cellId: cellInfo.cellId, outputId, height }]); - }, 10); + }, DOM.getWindow(this._listViewContainer), 10); } private pendingLayouts = new WeakMap(); @@ -784,7 +784,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD relayout(cell, height); r(); - }); + }, DOM.getWindow(this._listViewContainer)); this.pendingLayouts.set(cell, toDisposable(() => { layoutDisposable.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts index 1d39891624f..57938e24695 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts @@ -114,7 +114,7 @@ export class NotebookDiffOverviewRuler extends Themable { private _scheduleRender(): void { if (this._renderAnimationFrame === null) { - this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 16); + this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), DOM.getWindow(this._domNode.domNode), 16); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index c1dcf42d739..96edb0787a4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -180,7 +180,7 @@ class NotebookDiffEditorSerializer implements IEditorSerializer { } } -type SerializedNotebookEditorData = { resource: URI; viewType: string; options?: NotebookEditorInputOptions }; +type SerializedNotebookEditorData = { resource: URI; preferredResource: URI; viewType: string; options?: NotebookEditorInputOptions }; class NotebookEditorSerializer implements IEditorSerializer { canSerialize(): boolean { return true; @@ -189,6 +189,7 @@ class NotebookEditorSerializer implements IEditorSerializer { assertType(input instanceof NotebookEditorInput); const data: SerializedNotebookEditorData = { resource: input.resource, + preferredResource: input.preferredResource, viewType: input.viewType, options: input.options }; @@ -199,12 +200,12 @@ class NotebookEditorSerializer implements IEditorSerializer { if (!data) { return undefined; } - const { resource, viewType, options } = data; + const { resource, preferredResource, viewType, options } = data; if (!data || !URI.isUri(resource) || typeof viewType !== 'string') { return undefined; } - const input = NotebookEditorInput.create(instantiationService, resource, viewType, options); + const input = NotebookEditorInput.getOrCreate(instantiationService, resource, preferredResource, viewType, options); return input; } } @@ -639,7 +640,7 @@ class SimpleNotebookWorkingCopyEditorHandler extends Disposable implements IWork } createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput { - return NotebookEditorInput.create(this._instantiationService, workingCopy.resource, this._getViewType(workingCopy)!); + return NotebookEditorInput.getOrCreate(this._instantiationService, workingCopy.resource, undefined, this._getViewType(workingCopy)!); } private async _installHandler(): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index e2cceb4b1ec..e391c3c2d56 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1454,7 +1454,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._localStore.add(DOM.scheduleAtNextAnimationFrame(() => { hasPendingChangeContentHeight = false; this._updateScrollHeight(); - }, 100)); + }, DOM.getWindow(this._body), 100)); })); this._localStore.add(this._list.onDidRemoveOutputs(outputs => { @@ -2259,7 +2259,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }; if (this._list.inRenderingTransaction) { - const layoutDisposable = DOM.scheduleAtNextAnimationFrame(doLayout); + const layoutDisposable = DOM.scheduleAtNextAnimationFrame(doLayout, DOM.getWindow(this._body)); this._pendingLayouts?.set(cell, toDisposable(() => { layoutDisposable.dispose(); @@ -2973,7 +2973,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._webview?.ackHeight([...this._pendingOutputHeightAcks.values()]); this._pendingOutputHeightAcks.clear(); - }, -1); // -1 priority because this depends on calls to layoutNotebookCell, and that may be called multiple times before this runs + }, DOM.getWindow(this._body), -1); // -1 priority because this depends on calls to layoutNotebookCell, and that may be called multiple times before this runs } } diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts index c01228ddc92..1e207678d7d 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookServiceImpl.ts @@ -42,6 +42,7 @@ import { DiffEditorInputFactoryFunction, EditorInputFactoryFunction, EditorInput import { IExtensionService, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export class NotebookProviderInfoStore extends Disposable { @@ -62,7 +63,8 @@ export class NotebookProviderInfoStore extends Disposable { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IFileService private readonly _fileService: IFileService, - @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService + @INotebookEditorModelResolverService private readonly _notebookEditorModelResolverService: INotebookEditorModelResolverService, + @IUriIdentityService private readonly uriIdentService: IUriIdentityService, ) { super(); @@ -178,12 +180,18 @@ export class NotebookProviderInfoStore extends Disposable { }; const notebookEditorInputFactory: EditorInputFactoryFunction = ({ resource, options }) => { const data = CellUri.parse(resource); - let notebookUri: URI = resource; + let notebookUri: URI; + let cellOptions: IResourceEditorInput | undefined; + let preferredResource = resource; if (data) { - notebookUri = data.notebook; + // resource is a notebook cell + notebookUri = this.uriIdentService.asCanonicalUri(data.notebook); + preferredResource = data.notebook; cellOptions = { resource, options }; + } else { + notebookUri = this.uriIdentService.asCanonicalUri(resource); } if (!cellOptions) { @@ -191,8 +199,10 @@ export class NotebookProviderInfoStore extends Disposable { } const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; - return { editor: NotebookEditorInput.create(this._instantiationService, notebookUri, notebookProviderInfo.id), options: notebookOptions }; + const editor = NotebookEditorInput.getOrCreate(this._instantiationService, notebookUri, preferredResource, notebookProviderInfo.id); + return { editor, options: notebookOptions }; }; + const notebookUntitledEditorFactory: UntitledEditorInputFactoryFunction = async ({ resource, options }) => { const ref = await this._notebookEditorModelResolverService.resolve({ untitledResource: resource }, notebookProviderInfo.id); @@ -202,7 +212,7 @@ export class NotebookProviderInfoStore extends Disposable { ref!.dispose(); }); - return { editor: NotebookEditorInput.create(this._instantiationService, ref.object.resource, notebookProviderInfo.id), options }; + return { editor: NotebookEditorInput.getOrCreate(this._instantiationService, ref.object.resource, undefined, notebookProviderInfo.id), options }; }; const notebookDiffEditorInputFactory: DiffEditorInputFactoryFunction = ({ modified, original, label, description }) => { return { editor: NotebookDiffEditorInput.create(this._instantiationService, modified.resource!, label, description, original.resource!, notebookProviderInfo.id) }; diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts index ac1396b310f..32dc2032748 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts @@ -128,18 +128,19 @@ export class CellPartsCollection extends Disposable { private _scheduledOverlayUpdateExecutionState = this._register(new MutableDisposable()); constructor( + private readonly targetWindow: Window, private readonly contentParts: readonly CellContentPart[], private readonly overlayParts: readonly CellOverlayPart[] ) { super(); } - concatContentPart(other: readonly CellContentPart[]): CellPartsCollection { - return new CellPartsCollection(this.contentParts.concat(other), this.overlayParts); + concatContentPart(other: readonly CellContentPart[], targetWindow: Window): CellPartsCollection { + return new CellPartsCollection(targetWindow, this.contentParts.concat(other), this.overlayParts); } - concatOverlayPart(other: readonly CellOverlayPart[]): CellPartsCollection { - return new CellPartsCollection(this.contentParts, this.overlayParts.concat(other)); + concatOverlayPart(other: readonly CellOverlayPart[], targetWindow: Window): CellPartsCollection { + return new CellPartsCollection(targetWindow, this.contentParts, this.overlayParts.concat(other)); } scheduleRenderCell(element: ICellViewModel): void { @@ -161,7 +162,7 @@ export class CellPartsCollection extends Disposable { for (const part of this.overlayParts) { part.renderCell(element); } - }); + }, this.targetWindow); } unrenderCell(element: ICellViewModel): void { @@ -203,7 +204,7 @@ export class CellPartsCollection extends Disposable { for (const part of this.overlayParts) { part.updateState(viewCell, e); } - }); + }, this.targetWindow); } updateForExecutionState(viewCell: ICellViewModel, e: ICellExecutionStateChangedEvent) { @@ -215,6 +216,6 @@ export class CellPartsCollection extends Disposable { for (const part of this.overlayParts) { part.updateForExecutionState(viewCell, e); } - }); + }, this.targetWindow); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 501569e9984..9f727ebfe88 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -58,7 +58,7 @@ export class CodeCell extends Disposable { const cellEditorOptions = this._register(new CellEditorOptions(this.notebookEditor.getBaseCellEditorOptions(viewCell.language), this.notebookEditor.notebookOptions, this.configurationService)); this._outputContainerRenderer = this.instantiationService.createInstance(CellOutputContainer, notebookEditor, viewCell, templateData, { limit: outputDisplayLimit }); - this.cellParts = this._register(templateData.cellParts.concatContentPart([cellEditorOptions, this._outputContainerRenderer])); + this.cellParts = this._register(templateData.cellParts.concatContentPart([cellEditorOptions, this._outputContainerRenderer], DOM.getWindow(notebookEditor.getDomNode()))); // this.viewCell.layoutInfo.editorHeight or estimation when this.viewCell.layoutInfo.editorHeight === 0 const editorHeight = this.calculateInitEditorHeight(); @@ -146,7 +146,7 @@ export class CodeCell extends Disposable { this._pendingLayout?.dispose(); this._pendingLayout = DOM.modify(() => { this.cellParts.updateInternalLayoutNow(this.viewCell); - }); + }, DOM.getWindow(this.templateData.container)); } private updateForOutputHover() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index f54d78ac851..04f3c12567f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -289,7 +289,7 @@ export class NotebookCellList extends WorkbenchList implements ID if (this._isInLayout) { DOM.scheduleAtNextAnimationFrame(() => { updateVisibleRanges(); - }); + }, DOM.getWindow(container)); } updateVisibleRanges(); })); @@ -297,7 +297,7 @@ export class NotebookCellList extends WorkbenchList implements ID if (this._isInLayout) { DOM.scheduleAtNextAnimationFrame(() => { updateVisibleRanges(); - }); + }, DOM.getWindow(container)); } updateVisibleRanges(); })); @@ -369,7 +369,7 @@ export class NotebookCellList extends WorkbenchList implements ID } this._updateElementsInWebview(viewDiffs); - })); + }, DOM.getWindow(this.rowsContainer))); } })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index d9c13b6094e..f6f8aa92455 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -162,7 +162,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen this.notebookEditor)); const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'))); - const cellParts = new CellPartsCollection([ + const cellParts = new CellPartsCollection(DOM.getWindow(rootContainer), [ templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)), templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)), templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))), @@ -306,7 +306,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.notebookEditor)); const focusIndicatorPart = templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)); - const cellParts = new CellPartsCollection([ + const cellParts = new CellPartsCollection(DOM.getWindow(rootContainer), [ focusIndicatorPart, templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, editor)), templateDisposables.add(scopedInstaService.createInstance(CellProgressBar, editorPart, cellInputCollapsedContainer)), diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index c7b0471d676..390ed06f677 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -115,7 +115,7 @@ export class NotebookEditorContextKeys { layoutDisposable.add(DOM.scheduleAtNextAnimationFrame(() => { recomputeOutputsExistence(); - })); + }, DOM.getWindow(this._editor.getDomNode()))); }); }; diff --git a/src/vs/workbench/contrib/notebook/common/notebookDiffEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookDiffEditorInput.ts index 1b6a2db5743..5884848448e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookDiffEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookDiffEditorInput.ts @@ -24,8 +24,8 @@ class NotebookDiffEditorModel extends EditorModel implements INotebookDiffEditor export class NotebookDiffEditorInput extends DiffEditorInput { static create(instantiationService: IInstantiationService, resource: URI, name: string | undefined, description: string | undefined, originalResource: URI, viewType: string) { - const original = NotebookEditorInput.create(instantiationService, originalResource, viewType); - const modified = NotebookEditorInput.create(instantiationService, resource, viewType); + const original = NotebookEditorInput.getOrCreate(instantiationService, originalResource, undefined, viewType); + const modified = NotebookEditorInput.getOrCreate(instantiationService, resource, undefined, viewType); return instantiationService.createInstance(NotebookDiffEditorInput, name, description, original, modified, viewType); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index 1ac7010369c..1d6bba54100 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -41,8 +41,24 @@ export interface NotebookEditorInputOptions { export class NotebookEditorInput extends AbstractResourceEditorInput { - static create(instantiationService: IInstantiationService, resource: URI, viewType: string, options: NotebookEditorInputOptions = {}) { - return instantiationService.createInstance(NotebookEditorInput, resource, viewType, options); + private static EditorCache: Record = {}; + + static getOrCreate(instantiationService: IInstantiationService, resource: URI, preferredResource: URI | undefined, viewType: string, options: NotebookEditorInputOptions = {}) { + const cacheId = `${resource.toString()}|${viewType}|${options._workingCopy?.typeId}`; + let editor = NotebookEditorInput.EditorCache[cacheId]; + + if (!editor) { + editor = instantiationService.createInstance(NotebookEditorInput, resource, preferredResource, viewType, options); + NotebookEditorInput.EditorCache[cacheId] = editor; + + editor.onWillDispose(() => { + delete NotebookEditorInput.EditorCache[cacheId]; + }); + } else if (preferredResource) { + editor.setPreferredResource(preferredResource); + } + + return editor; } static readonly ID: string = 'workbench.input.notebook'; @@ -53,19 +69,19 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { constructor( resource: URI, + preferredResource: URI | undefined, public readonly viewType: string, public readonly options: NotebookEditorInputOptions, @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, @IFileDialogService private readonly _fileDialogService: IFileDialogService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, @IFileService fileService: IFileService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @IExtensionService extensionService: IExtensionService, @IEditorService editorService: IEditorService ) { - super(resource, undefined, labelService, fileService, filesConfigurationService); + super(resource, preferredResource, labelService, fileService, filesConfigurationService); this._defaultDirtyState = !!options.startDirty; // Automatically resolve this input when the "wanted" model comes to life via @@ -253,20 +269,12 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { // called when users rename a notebook document override async rename(group: GroupIdentifier, target: URI): Promise { if (this._editorModelReference) { - const contributedNotebookProviders = this._notebookService.getContributedNotebookTypes(target); + return { editor: { resource: target }, options: { override: this.viewType } }; - if (contributedNotebookProviders.find(provider => provider.id === this._editorModelReference!.object.viewType)) { - return this._move(group, target); - } } return undefined; } - private _move(_group: GroupIdentifier, newResource: URI): { editor: EditorInput } { - const editorInput = NotebookEditorInput.create(this._instantiationService, newResource, this.viewType); - return { editor: editorInput }; - } - override async revert(_group: GroupIdentifier, options?: IRevertOptions): Promise { if (this._editorModelReference && this._editorModelReference.object.isDirty()) { await this._editorModelReference.object.revert(options); @@ -336,7 +344,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { override toUntyped(): IResourceEditorInput { return { - resource: this.preferredResource, + resource: this.resource, options: { override: this.viewType } diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts index f8e70728176..c204630f49d 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookServiceImpl.test.ts @@ -13,6 +13,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IFileService } from 'vs/platform/files/common/files'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { NotebookProviderInfoStore } from 'vs/workbench/contrib/notebook/browser/services/notebookServiceImpl'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -43,7 +44,8 @@ suite('NotebookProviderInfoStore', function () { new class extends mock() { override hasProvider() { return true; } }, - new class extends mock() { } + new class extends mock() { }, + new class extends mock() { } ); disposables.add(store); diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 3b6417cc287..0d13de2bb2f 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -239,28 +239,24 @@ .scm-view .scm-input { height: 100%; + display: flex; + align-items: center; padding-left: 11px; - border-radius: 2px; } -.scm-view .scm-input .actions { - position: absolute; - top: 7px; - right: 20px; - z-index: 2; +.scm-view .scm-input .scm-editor .scm-editor-toolbar { + padding: 1px 3px 1px 1px; } -.scm-view .scm-input .actions .actions-container { - background-color: var(--vscode-input-background); +.scm-view .scm-input .scm-editor .scm-editor-toolbar.hidden { + display: none; } -.scm-view .scm-input .actions .action-label { - border-radius: 5px; - outline: 1px dashed var(--vscode-toolbar-hoverOutline); - outline-offset: -1px; +.scm-view .scm-input .scm-editor .scm-editor-toolbar.scroll-decoration { + box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset; } -.scm-view .scm-input .actions .action-label.codicon.codicon-debug-stop { +.scm-view .scm-input .scm-editor .scm-editor-toolbar .action-label.codicon.codicon-debug-stop { color: var(--vscode-icon-foreground) !important; } @@ -275,10 +271,12 @@ .scm-view .scm-editor { box-sizing: border-box; width: 100%; - height: 100%; display: flex; - flex-direction: column; - justify-content: center; + align-items: flex-start; + box-sizing: border-box; + border: 1px solid var(--vscode-input-border, transparent); + background-color: var(--vscode-input-background); + border-radius: 2px; } .scm-view .button-container { @@ -329,31 +327,23 @@ outline: 1px solid var(--vscode-panelInput-border); } -.scm-view .scm-editor-container { - position: relative; - box-sizing: border-box; - background-color: var(--vscode-input-background); - border: 1px solid var(--vscode-input-border, transparent); - border-radius: 2px; -} - -.scm-view .scm-editor-container.synthetic-focus, -.monaco-workbench .part.panel .scm-view .scm-editor-container.synthetic-focus { +.scm-view .scm-editor.synthetic-focus, +.monaco-workbench .part.panel .scm-view .scm-editor.synthetic-focus { outline: 1px solid var(--vscode-focusBorder); outline-offset: -1px; } -.scm-view .scm-editor-container.validation-info { +.scm-view .scm-editor.validation-info { outline: 1px solid var(--vscode-inputValidation-infoBorder) !important; outline-offset: -1px; } -.scm-view .scm-editor-container.validation-warning { +.scm-view .scm-editor.validation-warning { outline: 1px solid var(--vscode-inputValidation-warningBorder) !important; outline-offset: -1px; } -.scm-view .scm-editor-container.validation-error { +.scm-view .scm-editor.validation-error { outline: 1px solid var(--vscode-inputValidation-errorBorder) !important; outline-offset: -1px; } diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 924f9f477a1..5df3f7ae41d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -267,6 +267,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis markdownDescription: localize('inputFontSize', "Controls the font size for the input message in pixels."), default: 13 }, + 'scm.inputMaxLines': { + type: 'number', + markdownDescription: localize('inputMaxLines', "Controls the maximum number of lines that the input will auto-grow to."), + default: 10 + }, 'scm.alwaysShowRepositories': { type: 'boolean', markdownDescription: localize('alwaysShowRepository', "Controls whether repositories should always be visible in the Source Control view."), diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 492d2e20979..23a0f32b75e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -96,6 +96,7 @@ import { fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd'; import { FormatOnType } from 'vs/editor/contrib/format/browser/formatActions'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | IResourceNode | ISCMResource; @@ -1808,6 +1809,7 @@ class SCMInputWidget { private editorContainer: HTMLElement; private placeholderTextContainer: HTMLElement; private inputEditor: CodeEditorWidget; + private toolbarContainer: HTMLElement; private actionBar: ActionBar; private readonly disposables = new DisposableStore(); @@ -1837,7 +1839,7 @@ class SCMInputWidget { } this.clearValidation(); - this.editorContainer.classList.remove('synthetic-focus'); + this.element.classList.remove('synthetic-focus'); this.repositoryDisposables.clear(); this.repositoryIdContextKey.set(input?.repository.id); @@ -1958,11 +1960,8 @@ class SCMInputWidget { this.repositoryDisposables.add(input.onDidChangeEnablement(enabled => updateEnablement(enabled))); updateEnablement(input.enabled); - // ActionBar + // Toolbar const onDidChangeActionButton = () => { - // Update placeholder width to accommodate for the action bar - this.placeholderTextContainer.style.width = input.actionButton ? 'calc(100% - 26px)' : '100%'; - this.actionBar.clear(); if (!input.actionButton) { return; @@ -1976,6 +1975,7 @@ class SCMInputWidget { () => this.commandService.executeCommand(input.actionButton!.command.id, ...(input.actionButton!.command.arguments || []))); this.actionBar.push(action, { icon: true, label: false }); + this.layout(); }; this.repositoryDisposables.add(input.onDidChangeActionButton(onDidChangeActionButton, this)); @@ -2027,6 +2027,7 @@ class SCMInputWidget { this.element = append(container, $('.scm-editor')); this.editorContainer = append(this.element, $('.scm-editor-container')); this.placeholderTextContainer = append(this.editorContainer, $('.scm-editor-placeholder')); + this.toolbarContainer = append(this.element, $('.scm-editor-toolbar')); const fontFamily = this.getInputEditorFontFamily(); const fontSize = this.getInputEditorFontSize(); @@ -2049,7 +2050,10 @@ class SCMInputWidget { wrappingIndent: 'none', padding: { top: 2, bottom: 2 }, quickSuggestions: false, - scrollbar: { alwaysConsumeMouseWheel: false }, + scrollbar: { + alwaysConsumeMouseWheel: false, + vertical: 'hidden' + }, overflowWidgetsDomNode, formatOnType: true, renderWhitespace: 'none', @@ -2076,9 +2080,6 @@ class SCMInputWidget { ]) }; - this.actionBar = new ActionBar(append(this.element, $('.actions'))); - this.disposables.add(this.actionBar); - const services = new ServiceCollection([IContextKeyService, contextKeyService2]); const instantiationService2 = instantiationService.createChild(services); this.inputEditor = instantiationService2.createInstance(CodeEditorWidget, this.editorContainer, editorOptions, codeEditorWidgetOptions); @@ -2089,11 +2090,11 @@ class SCMInputWidget { this.scmViewService.focus(this.input.repository); } - this.editorContainer.classList.add('synthetic-focus'); + this.element.classList.add('synthetic-focus'); this.renderValidation(); })); this.disposables.add(this.inputEditor.onDidBlurEditorText(() => { - this.editorContainer.classList.remove('synthetic-focus'); + this.element.classList.remove('synthetic-focus'); setTimeout(() => { if (!this.validation || !this.validationHasFocus) { @@ -2113,6 +2114,9 @@ class SCMInputWidget { firstLineKey.set(viewPosition.lineNumber === 1 && viewPosition.column === 1); lastLineKey.set(viewPosition.lineNumber === lastLineNumber && viewPosition.column === lastLineCol); })); + this.disposables.add(this.inputEditor.onDidScrollChange(e => { + this.toolbarContainer.classList.toggle('scroll-decoration', e.scrollTop > 0); + })); const relevantSettings = [ 'scm.inputFontFamily', @@ -2153,16 +2157,23 @@ class SCMInputWidget { })); this.onDidChangeContentHeight = Event.signal(Event.filter(this.inputEditor.onDidContentSizeChange, e => e.contentHeightChanged, this.disposables)); + + // Toolbar + this.actionBar = new ActionBar(this.toolbarContainer); + this.disposables.add(this.actionBar); } getContentHeight(): number { const editorContentHeight = this.inputEditor.getContentHeight(); - return Math.min(editorContentHeight, 134); + const editorContextHeightMax = this.getInputEditorMaxHeight(); + + return Math.min(editorContentHeight, editorContextHeightMax); } layout(): void { const editorHeight = this.getContentHeight(); - const dimension = new Dimension(this.element.clientWidth - 2, editorHeight); + const toolbarWidth = this.toolbarContainer.clientWidth; + const dimension = new Dimension(this.element.clientWidth - toolbarWidth, editorHeight); if (dimension.width < 0) { this.lastLayoutWasTrash = true; @@ -2171,6 +2182,8 @@ class SCMInputWidget { this.lastLayoutWasTrash = false; this.inputEditor.layout(dimension); + this.placeholderTextContainer.style.width = `${dimension.width}px`; + this.toolbarContainer.classList.toggle('hidden', this.input?.actionButton === undefined); this.renderValidation(); if (this.shouldFocusAfterLayout) { @@ -2187,7 +2200,7 @@ class SCMInputWidget { } this.inputEditor.focus(); - this.editorContainer.classList.add('synthetic-focus'); + this.element.classList.add('synthetic-focus'); } hasFocus(): boolean { @@ -2197,9 +2210,9 @@ class SCMInputWidget { private renderValidation(): void { this.clearValidation(); - this.editorContainer.classList.toggle('validation-info', this.validation?.type === InputValidationType.Information); - this.editorContainer.classList.toggle('validation-warning', this.validation?.type === InputValidationType.Warning); - this.editorContainer.classList.toggle('validation-error', this.validation?.type === InputValidationType.Error); + this.element.classList.toggle('validation-info', this.validation?.type === InputValidationType.Information); + this.element.classList.toggle('validation-warning', this.validation?.type === InputValidationType.Warning); + this.element.classList.toggle('validation-error', this.validation?.type === InputValidationType.Error); if (!this.validation || !this.inputEditor.hasTextFocus()) { return; @@ -2208,16 +2221,16 @@ class SCMInputWidget { const disposables = new DisposableStore(); this.validationDisposable = this.contextViewService.showContextView({ - getAnchor: () => this.editorContainer, + getAnchor: () => this.element, render: container => { - this.editorContainer.style.borderBottomLeftRadius = '0'; - this.editorContainer.style.borderBottomRightRadius = '0'; + this.element.style.borderBottomLeftRadius = '0'; + this.element.style.borderBottomRightRadius = '0'; const validationContainer = append(container, $('.scm-editor-validation-container')); validationContainer.classList.toggle('validation-info', this.validation!.type === InputValidationType.Information); validationContainer.classList.toggle('validation-warning', this.validation!.type === InputValidationType.Warning); validationContainer.classList.toggle('validation-error', this.validation!.type === InputValidationType.Error); - validationContainer.style.width = `${this.editorContainer.clientWidth + 2}px`; + validationContainer.style.width = `${this.element.clientWidth + 2}px`; const element = append(validationContainer, $('.scm-editor-validation')); const message = this.validation!.message; @@ -2229,8 +2242,8 @@ class SCMInputWidget { disposables.add(tracker.onDidFocus(() => (this.validationHasFocus = true))); disposables.add(tracker.onDidBlur(() => { this.validationHasFocus = false; - this.editorContainer.style.borderBottomLeftRadius = '2px'; - this.editorContainer.style.borderBottomRightRadius = '2px'; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; this.contextViewService.hideContextView(); })); @@ -2239,8 +2252,8 @@ class SCMInputWidget { actionHandler: { callback: (link) => { openLinkFromMarkdown(this.openerService, link, message.isTrusted); - this.editorContainer.style.borderBottomLeftRadius = '2px'; - this.editorContainer.style.borderBottomRightRadius = '2px'; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; this.contextViewService.hideContextView(); }, disposables: disposables @@ -2253,8 +2266,8 @@ class SCMInputWidget { const actionbar = new ActionBar(actionsContainer); const action = new Action('scmInputWidget.validationMessage.close', localize('label.close', "Close"), ThemeIcon.asClassName(Codicon.close), true, () => { this.contextViewService.hideContextView(); - this.editorContainer.style.borderBottomLeftRadius = '2px'; - this.editorContainer.style.borderBottomRightRadius = '2px'; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; }); disposables.add(actionbar); actionbar.push(action, { icon: true, label: false }); @@ -2263,8 +2276,8 @@ class SCMInputWidget { }, onHide: () => { this.validationHasFocus = false; - this.editorContainer.style.borderBottomLeftRadius = '2px'; - this.editorContainer.style.borderBottomRightRadius = '2px'; + this.element.style.borderBottomLeftRadius = '2px'; + this.element.style.borderBottomRightRadius = '2px'; disposables.dispose(); }, anchorAlignment: AnchorAlignment.LEFT @@ -2289,6 +2302,19 @@ class SCMInputWidget { return this.configurationService.getValue('scm.inputFontSize'); } + private getInputEditorMaxLines(): number { + return this.configurationService.getValue('scm.inputMaxLines'); + } + + private getInputEditorMaxHeight(): number { + const maxLines = this.getInputEditorMaxLines(); + const fontSize = this.getInputEditorFontSize(); + const lineHeight = this.computeLineHeight(fontSize); + const { top, bottom } = this.inputEditor.getOption(EditorOption.padding); + + return maxLines * lineHeight + top + bottom; + } + private computeLineHeight(fontSize: number): number { return Math.round(fontSize * 1.5); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts b/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts index 09c35778b5a..c603d810d2b 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPaneContainer.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/scm'; import { localize } from 'vs/nls'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { VIEWLET_ID } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMViewService, VIEWLET_ID } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -21,6 +21,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont export class SCMViewPaneContainer extends ViewPaneContainer { constructor( + @ISCMViewService private readonly scmViewService: ISCMViewService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @@ -47,4 +48,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer { override getTitle(): string { return localize('source control', "Source Control"); } + + override getActionsContext(): unknown { + return this.scmViewService.visibleRepositories.length === 1 ? this.scmViewService.visibleRepositories[0].provider : undefined; + } + } diff --git a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts index c14c5fedbea..146eff9fad7 100644 --- a/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts +++ b/src/vs/workbench/contrib/search/browser/quickTextSearch/textSearchQuickAccess.ts @@ -29,7 +29,7 @@ import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/ import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { IPatternInfo, ITextQuery, VIEW_ID } from 'vs/workbench/services/search/common/search'; -export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '% '; +export const TEXT_SEARCH_QUICK_ACCESS_PREFIX = '%'; const DEFAULT_TEXT_QUERY_BUILDER_OPTIONS: ITextQueryBuilderOptions = { _reason: 'quickAccessSearch', @@ -83,6 +83,9 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider, token: CancellationToken, runOptions?: IQuickAccessProviderRunOptions): IDisposable { const disposables = new DisposableStore(); + if (TEXT_SEARCH_QUICK_ACCESS_PREFIX.length < picker.value.length) { + picker.valueSelection = [TEXT_SEARCH_QUICK_ACCESS_PREFIX.length, picker.value.length]; + } disposables.add(super.provide(picker, token, runOptions)); disposables.add(picker.onDidHide(() => this.searchModel.searchResult.toggleHighlights(false))); disposables.add(picker.onDidAccept(() => this.searchModel.searchResult.toggleHighlights(false))); @@ -272,7 +275,9 @@ export class TextSearchQuickAccess extends PickerQuickAccessProvider this._getPicksFromMatches(asyncResults, MAX_FILES_SHOWN - matches.length)) + .then(asyncResults => (asyncResults.length + syncResult.length === 0) ? [{ + label: localize('noAnythingResults', "No matching results") + }] : this._getPicksFromMatches(asyncResults, MAX_FILES_SHOWN - matches.length)) .then(picks => { if (picks.length > 0) { this.searchModel.searchResult.toggleHighlights(true); diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 68f64157179..ec6a10068f7 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -41,7 +41,7 @@ import { CellSearchModel, ICellMatch, contentMatchesToTextSearchMatches, isIFile import { INotebookSearchService } from 'vs/workbench/contrib/search/common/notebookSearch'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; import { IFileMatch, IPatternInfo, ISearchComplete, ISearchConfigurationProperties, ISearchProgressItem, ISearchRange, ISearchService, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchPreviewOptions, ITextSearchResult, ITextSearchStats, OneLineRange, resultIsMatch, SearchCompletionExitCode, SearchSortOrder } from 'vs/workbench/services/search/common/search'; -import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; +import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class Match { @@ -245,7 +245,7 @@ export class CellMatch { return; } this.cell.resolveTextModel().then((textModel) => { - const textResultsWithContext = addContextToEditorMatches(textSearchMatches, textModel, this.parent.parent().query!); + const textResultsWithContext = getTextSearchMatchWithModelContext(textSearchMatches, textModel, this.parent.parent().query!); const contexts = textResultsWithContext.filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)); contexts.map(context => ({ ...context, lineNumber: context.lineNumber + 1 })) .forEach((context) => { this._context.set(context.lineNumber, context.text); }); @@ -567,10 +567,7 @@ export class FileMatch extends Disposable implements IFileMatch { }); }); - this.addContext( - addContextToEditorMatches(textSearchResults, model, this.parent().parent().query!) - .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) - .map(context => ({ ...context, lineNumber: context.lineNumber + 1 }))); + this.addContext(getTextSearchMatchWithModelContext(textSearchResults, model, this.parent().parent().query!)); this._onChange.fire({ forceUpdateModel: modelChange }); this.updateHighlights(); @@ -1041,7 +1038,7 @@ export class FolderMatch extends Disposable { } public createIntermediateFolderMatch(resource: URI, id: string, index: number, query: ITextQuery, baseWorkspaceFolder: FolderMatchWorkspaceRoot): FolderMatchWithResource { - const folderMatch = this.instantiationService.createInstance(FolderMatchWithResource, resource, id, index, query, this, this._searchResult, baseWorkspaceFolder); + const folderMatch = this._register(this.instantiationService.createInstance(FolderMatchWithResource, resource, id, index, query, this, this._searchResult, baseWorkspaceFolder)); this.configureIntermediateMatch(folderMatch); this.doAddFolder(folderMatch); return folderMatch; @@ -1049,7 +1046,7 @@ export class FolderMatch extends Disposable { public configureIntermediateMatch(folderMatch: FolderMatchWithResource) { const disposable = folderMatch.onChange((event) => this.onFolderChange(folderMatch, event)); - folderMatch.onDispose(() => disposable.dispose()); + this._register(folderMatch.onDispose(() => disposable.dispose())); } clear(clearingAll = false): void { @@ -1176,7 +1173,9 @@ export class FolderMatch extends Disposable { updated.push(existingFileMatch); - existingFileMatch.addContext(rawFileMatch.results); + if (rawFileMatch.results && rawFileMatch.results.length > 0) { + existingFileMatch.addContext(rawFileMatch.results); + } } else { if (this instanceof FolderMatchWorkspaceRoot || this instanceof FolderMatchNoRoot) { const fileMatch = this.createAndConfigureFileMatch(rawFileMatch, searchInstanceID); @@ -1379,7 +1378,7 @@ export class FolderMatchWorkspaceRoot extends FolderMatchWithResource { ); parent.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => parent.onFileChange(fileMatch, didRemove)); - fileMatch.onDispose(() => disposable.dispose()); + this._register(fileMatch.onDispose(() => disposable.dispose())); return fileMatch; } @@ -1431,17 +1430,17 @@ export class FolderMatchNoRoot extends FolderMatch { } createAndConfigureFileMatch(rawFileMatch: IFileMatch, searchInstanceID: string): FileMatch { - const fileMatch = this.instantiationService.createInstance( + const fileMatch = this._register(this.instantiationService.createInstance( FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch, null, - searchInstanceID); + searchInstanceID)); this.doAddFile(fileMatch); const disposable = fileMatch.onChange(({ didRemove }) => this.onFileChange(fileMatch, didRemove)); - fileMatch.onDispose(() => disposable.dispose()); + this._register(fileMatch.onDispose(() => disposable.dispose())); return fileMatch; } } @@ -1590,7 +1589,7 @@ export class SearchResult extends Disposable { private _showHighlights: boolean = false; private _query: ITextQuery | null = null; private _rangeHighlightDecorations: RangeHighlightDecorations; - private disposePastResults: () => void = () => { }; + private disposePastResults: () => Promise = () => Promise.resolve(); private _isDirty = false; private _onWillChangeModelListener: IDisposable | undefined; private _onDidChangeModelListener: IDisposable | undefined; @@ -1674,10 +1673,11 @@ export class SearchResult extends Disposable { set query(query: ITextQuery | null) { // When updating the query we could change the roots, so keep a reference to them to clean up when we trigger `disposePastResults` const oldFolderMatches = this.folderMatches(); - new Promise(resolve => this.disposePastResults = resolve) - .then(() => oldFolderMatches.forEach(match => match.clear())) - .then(() => oldFolderMatches.forEach(match => match.dispose())) - .then(() => this._isDirty = false); + this.disposePastResults = async () => { + oldFolderMatches.forEach(match => match.clear()); + oldFolderMatches.forEach(match => match.dispose()); + this._isDirty = false; + }; this._rangeHighlightDecorations.removeHighlightRange(); this._folderMatchesMap = TernarySearchTree.forUris(key => this.uriIdentityService.extUri.ignorePathCasing(key)); @@ -1736,12 +1736,12 @@ export class SearchResult extends Disposable { private _createBaseFolderMatch(resource: URI | null, id: string, index: number, query: ITextQuery): FolderMatch { let folderMatch: FolderMatch; if (resource) { - folderMatch = this.instantiationService.createInstance(FolderMatchWorkspaceRoot, resource, id, index, query, this); + folderMatch = this._register(this.instantiationService.createInstance(FolderMatchWorkspaceRoot, resource, id, index, query, this)); } else { - folderMatch = this.instantiationService.createInstance(FolderMatchNoRoot, id, index, query, this); + folderMatch = this._register(this.instantiationService.createInstance(FolderMatchNoRoot, id, index, query, this)); } const disposable = folderMatch.onChange((event) => this._onChange.fire(event)); - folderMatch.onDispose(() => disposable.dispose()); + this._register(folderMatch.onDispose(() => disposable.dispose())); return folderMatch; } @@ -1922,13 +1922,13 @@ export class SearchResult extends Disposable { this._rangeHighlightDecorations.removeHighlightRange(); } - override dispose(): void { + override async dispose(): Promise { this._onWillChangeModelListener?.dispose(); this._onDidChangeModelListener?.dispose(); - this.disposePastResults(); - this.disposeMatches(); this._rangeHighlightDecorations.dispose(); + this.disposeMatches(); super.dispose(); + await this.disposePastResults(); } } @@ -2080,7 +2080,7 @@ export class SearchModel extends Disposable { this._searchResult.query = this._searchQuery; - const progressEmitter = new Emitter(); + const progressEmitter = this._register(new Emitter()); this._replacePattern = new ReplacePattern(this.replaceString, this._searchQuery.contentPattern); // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path @@ -2099,14 +2099,21 @@ export class SearchModel extends Disposable { } const start = Date.now(); + let event: IDisposable | undefined; - Promise.race([asyncResults, Event.toPromise(progressEmitter.event)]).finally(() => { + const progressEmitterPromise = new Promise(resolve => { + event = Event.once(progressEmitter.event)(resolve); + return event; + }); + + Promise.race([asyncResults, progressEmitterPromise]).finally(() => { /* __GDPR__ "searchResultsFirstRender" : { "owner": "roblourens", "duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } */ + event?.dispose(); this.telemetryService.publicLog('searchResultsFirstRender', { duration: Date.now() - start }); }); @@ -2331,9 +2338,9 @@ export class RangeHighlightDecorations implements IDisposable { dispose() { if (this._model) { this.removeHighlightRange(); - this._modelDisposables.dispose(); this._model = null; } + this._modelDisposables.dispose(); } private static readonly _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({ diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 593545994a1..f37b2f4e202 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1281,13 +1281,6 @@ export class SearchView extends ViewPane { } editor = editor ?? this.editorService.activeTextEditorControl; - if (isDiffEditor(editor)) { - if (editor.getOriginalEditor().hasTextFocus()) { - editor = editor.getOriginalEditor(); - } else { - editor = editor.getModifiedEditor(); - } - } if (!editor) { return null; @@ -2147,7 +2140,17 @@ export function getEditorSelectionFromMatch(element: FileMatchOrMatch, viewModel return undefined; } -export function getSelectionTextFromEditor(allowUnselectedWord: boolean, editor: IEditor): string | null { +export function getSelectionTextFromEditor(allowUnselectedWord: boolean, activeEditor: IEditor): string | null { + + let editor = activeEditor; + + if (isDiffEditor(editor)) { + if (editor.getOriginalEditor().hasTextFocus()) { + editor = editor.getOriginalEditor(); + } else { + editor = editor.getModifiedEditor(); + } + } if (!isCodeEditor(editor) || !editor.hasModel()) { return null; diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index ddaf7dcb2d5..5e39773a0cc 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -18,16 +18,18 @@ import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSea import { ILabelService } from 'vs/platform/label/common/label'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; import { createFileUriFromPathFromRoot, stubModelService, stubNotebookEditorService } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Search Actions', () => { let instantiationService: TestInstantiationService; let counter: number; + const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { instantiationService = new TestInstantiationService(); - instantiationService.stub(IModelService, stubModelService(instantiationService)); - instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); + instantiationService.stub(IModelService, stubModelService(instantiationService, (e) => store.add(e))); + instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService, (e) => store.add(e))); instantiationService.stub(IKeybindingService, {}); instantiationService.stub(ILabelService, { getUriBasenameLabel: (uri: URI) => '' }); instantiationService.stub(IKeybindingService, 'resolveKeybinding', (keybinding: Keybinding) => USLayoutResolvedKeybinding.resolveKeybinding(keybinding, OS)); @@ -113,14 +115,18 @@ suite('Search Actions', () => { }; const searchModel = instantiationService.createInstance(SearchModel); + store.add(searchModel); const folderMatch = instantiationService.createInstance(FolderMatch, URI.file('somepath'), '', 0, { type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { pattern: '' } }, searchModel.searchResult, searchModel.searchResult, null); - return instantiationService.createInstance(FileMatch, { + store.add(folderMatch); + const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, folderMatch, rawMatch, null, ''); + store.add(fileMatch); + return fileMatch; } function aMatch(fileMatch: FileMatch): Match { diff --git a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts index 715554ed96a..140f3ec64d1 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchModel.test.ts @@ -40,6 +40,7 @@ import { INotebookSearchService } from 'vs/workbench/contrib/search/common/noteb import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const nullEvent = new class { id: number = -1; @@ -65,6 +66,7 @@ const lineOneRange = new OneLineRange(1, 0, 1); suite('SearchModel', () => { let instantiationService: TestInstantiationService; + const store = ensureNoDisposablesAreLeakedInTestSuite(); const testSearchStats: IFileSearchStats = { fromCache: false, @@ -92,7 +94,11 @@ suite('SearchModel', () => { instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); instantiationService.stub(ISearchService, {}); instantiationService.stub(ISearchService, 'textSearch', Promise.resolve({ results: [] })); - instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); + const fileService = new FileService(new NullLogService()); + store.add(fileService); + const uriIdentityService = new UriIdentityService(fileService); + store.add(uriIdentityService); + instantiationService.stub(IUriIdentityService, uriIdentityService); instantiationService.stub(ILogService, new NullLogService()); }); @@ -164,12 +170,18 @@ suite('SearchModel', () => { function canceleableSearchService(tokenSource: CancellationTokenSource): ISearchService { return { textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { - token?.onCancellationRequested(() => tokenSource.cancel()); + const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); + if (disposable) { + store.add(disposable); + } return this.textSearchSplitSyncAsync(query, token, onProgress).asyncResults; }, fileSearch(query: IFileQuery, token?: CancellationToken): Promise { - token?.onCancellationRequested(() => tokenSource.cancel()); + const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); + if (disposable) { + store.add(disposable); + } return new Promise(resolve => { queueMicrotask(() => { resolve({}); @@ -177,7 +189,10 @@ suite('SearchModel', () => { }); }, textSearchSplitSyncAsync(query: ITextQuery, token?: CancellationToken | undefined, onProgress?: ((result: ISearchProgressItem) => void) | undefined): { syncResults: ISearchComplete; asyncResults: Promise } { - token?.onCancellationRequested(() => tokenSource.cancel()); + const disposable = token?.onCancellationRequested(() => tokenSource.cancel()); + if (disposable) { + store.add(disposable); + } return { syncResults: { results: [], @@ -219,7 +234,10 @@ suite('SearchModel', () => { completeData: Promise; allScannedFiles: Promise; } { - token?.onCancellationRequested(() => tokenSource?.cancel()); + const disposable = token?.onCancellationRequested(() => tokenSource?.cancel()); + if (disposable) { + store.add(disposable); + } const localResults = new ResourceMap(uri => uri.path); results.forEach(r => { @@ -252,6 +270,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + store.add(testObject); await testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; const actual = testObject.searchResult.matches(); @@ -352,6 +371,7 @@ suite('SearchModel', () => { const notebookSearchService = instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([aRawMatchWithCells('/1', cellMatchMd, cellMatchCode)], undefined)); const notebookSearch = sinon.spy(notebookSearchService, "notebookSearch"); const model: SearchModel = instantiationService.createInstance(SearchModel); + store.add(model); await model.search({ contentPattern: { pattern: 'test' }, type: QueryType.Text, folderQueries }).asyncResults; const actual = model.searchResult.matches(); @@ -404,6 +424,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + store.add(testObject); await testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; assert.ok(target.calledThrice); @@ -421,6 +442,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject = instantiationService.createInstance(SearchModel); + store.add(testObject); const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; return result.then(() => { @@ -443,6 +465,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject = instantiationService.createInstance(SearchModel); + store.add(testObject); const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; return result.then(() => { @@ -466,6 +489,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject = instantiationService.createInstance(SearchModel); + store.add(testObject); const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; return result.then(() => { }, () => { @@ -488,6 +512,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject = instantiationService.createInstance(SearchModel); + store.add(testObject); const result = testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; deferredPromise.cancel(); @@ -511,6 +536,7 @@ suite('SearchModel', () => { instantiationService.stub(ISearchService, searchServiceWithResults(results, { limitHit: false, messages: [], results: [] })); instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + store.add(testObject); await testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }).asyncResults; assert.ok(!testObject.searchResult.isEmpty()); @@ -522,9 +548,11 @@ suite('SearchModel', () => { test('Search Model: Previous search is cancelled when new search is called', async () => { const tokenSource = new CancellationTokenSource(); + store.add(tokenSource); instantiationService.stub(ISearchService, canceleableSearchService(tokenSource)); instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], tokenSource)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + store.add(testObject); testObject.search({ contentPattern: { pattern: 'somestring' }, type: QueryType.Text, folderQueries }); instantiationService.stub(ISearchService, searchServiceWithResults([])); instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); @@ -542,6 +570,7 @@ suite('SearchModel', () => { instantiationService.stub(INotebookSearchService, notebookSearchServiceWithInfo([], undefined)); const testObject: SearchModel = instantiationService.createInstance(SearchModel); + store.add(testObject); await testObject.search({ contentPattern: { pattern: 're' }, type: QueryType.Text, folderQueries }).asyncResults; testObject.replaceString = 'hello'; let match = testObject.searchResult.matches()[0].matches()[0]; @@ -578,14 +607,18 @@ suite('SearchModel', () => { const config = new TestConfigurationService(); config.setUserConfiguration('search', { searchOnType: true }); instantiationService.stub(IConfigurationService, config); - return instantiationService.createInstance(ModelService); + const modelService = instantiationService.createInstance(ModelService); + store.add(modelService); + return modelService; } function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IEditorService, new TestEditorService()); - return instantiationService.createInstance(NotebookEditorWidgetService); + const notebookEditorWidgetService = instantiationService.createInstance(NotebookEditorWidgetService); + store.add(notebookEditorWidgetService); + return notebookEditorWidgetService; } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts index 4eae2669c1c..ff2c6a9039e 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchNotebookHelpers.test.ts @@ -17,6 +17,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { createFileUriFromPathFromRoot, stubModelService, stubNotebookEditorService } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; import { IModelService } from 'vs/editor/common/services/model'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('searchNotebookHelpers', () => { let instantiationService: TestInstantiationService; @@ -28,12 +29,16 @@ suite('searchNotebookHelpers', () => { let markdownContentResults: ITextSearchMatch[]; let codeContentResults: ITextSearchMatch[]; let codeWebviewResults: ITextSearchMatch[]; + const store = ensureNoDisposablesAreLeakedInTestSuite(); let counter: number = 0; setup(() => { instantiationService = new TestInstantiationService(); - instantiationService.stub(IModelService, stubModelService(instantiationService)); - instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); + store.add(instantiationService); + const modelService = stubModelService(instantiationService, (e) => store.add(e)); + const notebookEditorService = stubNotebookEditorService(instantiationService, (e) => store.add(e)); + instantiationService.stub(IModelService, modelService); + instantiationService.stub(INotebookEditorService, notebookEditorService); mdInputCell = { cellKind: CellKind.Markup, textBuffer: { getLineContent(lineNumber: number): string { @@ -201,14 +206,19 @@ suite('searchNotebookHelpers', () => { }; const searchModel = instantiationService.createInstance(SearchModel); + store.add(searchModel); const folderMatch = instantiationService.createInstance(FolderMatch, URI.file('somepath'), '', 0, { type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { pattern: '' } }, searchModel.searchResult, searchModel.searchResult, null); - return instantiationService.createInstance(FileMatch, { + const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, folderMatch, rawMatch, null, ''); + store.add(folderMatch); + store.add(fileMatch); + + return fileMatch; } }); }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts index 923d74b550d..ad53f73d11c 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchResult.test.ts @@ -35,19 +35,25 @@ import { addToSearchResult, createFileUriFromPathFromRoot, getRootName } from 'v import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; const lineOneRange = new OneLineRange(1, 0, 1); suite('SearchResult', () => { let instantiationService: TestInstantiationService; + const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IModelService, stubModelService(instantiationService)); instantiationService.stub(INotebookEditorService, stubNotebookEditorService(instantiationService)); - instantiationService.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); + const fileService = new FileService(new NullLogService()); + store.add(fileService); + const uriIdentityService = new UriIdentityService(fileService); + store.add(uriIdentityService); + instantiationService.stub(IUriIdentityService, uriIdentityService); instantiationService.stubPromise(IReplaceService, {}); instantiationService.stub(IReplaceService, 'replace', () => Promise.resolve(null)); instantiationService.stub(ILabelService, new MockLabelService()); @@ -164,7 +170,9 @@ suite('SearchResult', () => { test('Match -> FileMatch -> SearchResult hierarchy exists', function () { const searchModel = instantiationService.createInstance(SearchModel); + store.add(searchModel); const searchResult = instantiationService.createInstance(SearchResult, searchModel); + store.add(searchResult); const fileMatch = aFileMatch('far/boo', searchResult); const lineMatch = new Match(fileMatch, ['foo bar'], new OneLineRange(0, 0, 3), new OneLineRange(1, 0, 3)); @@ -283,8 +291,8 @@ suite('SearchResult', () => { aRawMatch('/2', new TextSearchMatch('preview 2', lineOneRange))]); - testObject.matches()[0].onDispose(target1); - testObject.matches()[1].onDispose(target2); + store.add(testObject.matches()[0].onDispose(target1)); + store.add(testObject.matches()[1].onDispose(target2)); testObject.dispose(); @@ -300,7 +308,7 @@ suite('SearchResult', () => { aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); const objectToRemove = testObject.matches()[0]; - testObject.onChange(target); + store.add(testObject.onChange(target)); testObject.remove(objectToRemove); @@ -317,7 +325,7 @@ suite('SearchResult', () => { aRawMatch('/2', new TextSearchMatch('preview 2', lineOneRange))]); const arrayToRemove = testObject.matches(); - testObject.onChange(target); + store.add(testObject.onChange(target)); testObject.remove(arrayToRemove); @@ -362,7 +370,8 @@ suite('SearchResult', () => { addToSearchResult(testObject, [ aRawMatch('/1', new TextSearchMatch('preview 1', lineOneRange))]); - testObject.onChange(target); + + store.add(testObject.onChange(target)); const objectToRemove = testObject.matches()[0]; testObject.replace(objectToRemove); @@ -399,7 +408,7 @@ suite('SearchResult', () => { const arrayToRemove = [folderMatch, fileMatch, match]; const expectedArrayResult = folderMatch.allDownstreamFileMatches().concat([fileMatch, match.parent()]); - testObject.onChange(target); + store.add(testObject.onChange(target)); testObject.batchRemove(arrayToRemove); assert.ok(target.calledOnce); @@ -428,7 +437,7 @@ suite('SearchResult', () => { const arrayToRemove = [folderMatch, fileMatch, match]; - testObject.onChange(target); + store.add(testObject.onChange(target)); await testObject.batchReplace(arrayToRemove); assert.ok(target.calledOnce); @@ -491,7 +500,7 @@ suite('SearchResult', () => { const expectedArrayResult = folderMatch.allDownstreamFileMatches(); - testObject.onChange(target); + store.add(testObject.onChange(target)); testObject.remove(folderMatch); assert.ok(target.calledOnce); assert.deepStrictEqual([{ elements: expectedArrayResult, removed: true, added: false, clearingAll: false }], target.args[0]); @@ -505,7 +514,7 @@ suite('SearchResult', () => { const expectedArrayResult = folderMatch.allDownstreamFileMatches(); - testObject.onChange(target); + store.add(testObject.onChange(target)); await testObject.batchReplace([folderMatch]); assert.deepStrictEqual([{ elements: expectedArrayResult, removed: true, added: false }], target.args[0]); @@ -520,13 +529,17 @@ suite('SearchResult', () => { results: lineMatches }; const root = searchResult?.folderMatches()[0]; - return instantiationService.createInstance(FileMatch, { + const fileMatch = instantiationService.createInstance(FileMatch, { pattern: '' }, undefined, undefined, root, rawMatch, null, ''); + + store.add(fileMatch); + return fileMatch; } function aSearchResult(): SearchResult { const searchModel = instantiationService.createInstance(SearchModel); + store.add(searchModel); searchModel.searchResult.query = { type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { pattern: '' @@ -551,14 +564,18 @@ suite('SearchResult', () => { const config = new TestConfigurationService(); config.setUserConfiguration('search', { searchOnType: true }); instantiationService.stub(IConfigurationService, config); - return instantiationService.createInstance(ModelService); + const modelService = instantiationService.createInstance(ModelService); + store.add(modelService); + return modelService; } function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IEditorService, new TestEditorService()); - return instantiationService.createInstance(NotebookEditorWidgetService); + const notebookEditorWidgetService = instantiationService.createInstance(NotebookEditorWidgetService); + store.add(notebookEditorWidgetService); + return notebookEditorWidgetService; } function getPopulatedSearchResult() { diff --git a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts index 71e860b9f75..54ace56ea38 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchTestCommon.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IDisposable } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IModelService } from 'vs/editor/common/services/model'; @@ -43,19 +44,23 @@ export function getRootName(): string { } } -export function stubModelService(instantiationService: TestInstantiationService): IModelService { +export function stubModelService(instantiationService: TestInstantiationService, addDisposable: (e: IDisposable) => void): IModelService { instantiationService.stub(IThemeService, new TestThemeService()); const config = new TestConfigurationService(); config.setUserConfiguration('search', { searchOnType: true }); instantiationService.stub(IConfigurationService, config); - return instantiationService.createInstance(ModelService); + const modelService = instantiationService.createInstance(ModelService); + addDisposable(modelService); + return modelService; } -export function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { +export function stubNotebookEditorService(instantiationService: TestInstantiationService, addDisposable: (e: IDisposable) => void): INotebookEditorService { instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.stub(IEditorService, new TestEditorService()); - return instantiationService.createInstance(NotebookEditorWidgetService); + const notebookEditorWidgetService = instantiationService.createInstance(NotebookEditorWidgetService); + addDisposable(notebookEditorWidgetService); + return notebookEditorWidgetService; } export function addToSearchResult(searchResult: SearchResult, allRaw: IFileMatch[], searchInstanceID = '') { diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index f73f00799f6..76b49f5f2bf 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -6,16 +6,11 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IModelService } from 'vs/editor/common/services/model'; -import { ModelService } from 'vs/editor/common/services/modelService'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { FileService } from 'vs/platform/files/common/fileService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -25,25 +20,25 @@ import { MockLabelService } from 'vs/workbench/services/label/test/common/mockLa import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { TestEditorGroupsService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { NotebookEditorWidgetService } from 'vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl'; -import { createFileUriFromPathFromRoot, getRootName } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { createFileUriFromPathFromRoot, getRootName, stubModelService, stubNotebookEditorService } from 'vs/workbench/contrib/search/test/browser/searchTestCommon'; +import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; + const store = ensureNoDisposablesAreLeakedInTestSuite(); setup(() => { instantiation = new TestInstantiationService(); instantiation.stub(ILanguageConfigurationService, TestLanguageConfigurationService); - instantiation.stub(IModelService, stubModelService(instantiation)); - instantiation.stub(INotebookEditorService, stubNotebookEditorService(instantiation)); + instantiation.stub(IModelService, stubModelService(instantiation, (e) => store.add(e))); + instantiation.stub(INotebookEditorService, stubNotebookEditorService(instantiation, (e) => store.add(e))); instantiation.set(IWorkspaceContextService, new TestContextService(TestWorkspace)); - instantiation.stub(IUriIdentityService, new UriIdentityService(new FileService(new NullLogService()))); + const fileService = new FileService(new NullLogService()); + store.add(fileService); + const uriIdentityService = new UriIdentityService(fileService); + store.add(uriIdentityService); + instantiation.stub(IUriIdentityService, uriIdentityService); instantiation.stub(ILabelService, new MockLabelService()); instantiation.stub(ILogService, new NullLogService()); }); @@ -182,22 +177,29 @@ suite('Search - Viewlet', () => { resource: URI.file('/' + path), results: lineMatches }; - return instantiation.createInstance(FileMatch, { + const fileMatch = instantiation.createInstance(FileMatch, { pattern: '' }, undefined, undefined, parentFolder ?? aFolderMatch('', 0), rawMatch, null, ''); + store.add(fileMatch); + return fileMatch; } function aFolderMatch(path: string, index: number, parent?: SearchResult): FolderMatch { const searchModel = instantiation.createInstance(SearchModel); - return instantiation.createInstance(FolderMatch, createFileUriFromPathFromRoot(path), path, index, { + store.add(searchModel); + const folderMatch = instantiation.createInstance(FolderMatch, createFileUriFromPathFromRoot(path), path, index, { type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { pattern: '' } }, parent ?? aSearchResult().folderMatches()[0], searchModel.searchResult, null); + store.add(folderMatch); + return folderMatch; } function aSearchResult(): SearchResult { const searchModel = instantiation.createInstance(SearchModel); + store.add(searchModel); + searchModel.searchResult.query = { type: QueryType.Text, folderQueries: [{ folder: createFileUriFromPathFromRoot() }], contentPattern: { pattern: '' @@ -205,21 +207,4 @@ suite('Search - Viewlet', () => { }; return searchModel.searchResult; } - - function stubModelService(instantiationService: TestInstantiationService): IModelService { - instantiationService.stub(IThemeService, new TestThemeService()); - - const config = new TestConfigurationService(); - config.setUserConfiguration('search', { searchOnType: true }); - instantiationService.stub(IConfigurationService, config); - - return instantiationService.createInstance(ModelService); - } - - function stubNotebookEditorService(instantiationService: TestInstantiationService): INotebookEditorService { - instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); - instantiationService.stub(IContextKeyService, new MockContextKeyService()); - instantiationService.stub(IEditorService, new TestEditorService()); - return instantiationService.createInstance(NotebookEditorWidgetService); - } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index de489c3a4dd..cedb103f398 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -53,7 +53,6 @@ export class TerminalViewPane extends ViewPane { private _parentDomElement: HTMLElement | undefined; private _terminalTabbedView?: TerminalTabbedView; get terminalTabbedView(): TerminalTabbedView | undefined { return this._terminalTabbedView; } - private _isWelcomeShowing: boolean = false; private _isInitialized: boolean = false; private _newDropdown: DropdownWithPrimaryActionViewItem | undefined; private readonly _dropdownMenu: IMenu; @@ -87,13 +86,18 @@ export class TerminalViewPane extends ViewPane { })); this._register(this._terminalService.onDidChangeInstances(() => { - if (!this._isWelcomeShowing) { - return; + // If the first terminal is opened, hide the welcome view + // and if the last one is closed, show it again + if (this._hasWelcomeScreen() && this._terminalService.instances.length <= 1) { + this._onDidChangeViewWelcomeState.fire(); } - this._isWelcomeShowing = true; - this._onDidChangeViewWelcomeState.fire(); - if (!this._terminalTabbedView && this._parentDomElement) { + if (!this._parentDomElement) { return; } + // If we do not have the tab view yet, create it now. + if (!this._terminalTabbedView) { this._createTabsView(); + } + // If we just opened our first terminal, layout + if (this._terminalService.instances.length === 1) { this.layoutBody(this._parentDomElement.offsetHeight, this._parentDomElement.offsetWidth); } })); @@ -200,7 +204,7 @@ export class TerminalViewPane extends ViewPane { this._register(this.onDidChangeBodyVisibility(async visible => { this._viewShowing.set(visible); if (visible) { - if (!this._terminalService.isProcessSupportRegistered) { + if (this._hasWelcomeScreen()) { this._onDidChangeViewWelcomeState.fire(); } this._initializeTerminal(false); @@ -319,9 +323,12 @@ export class TerminalViewPane extends ViewPane { } } + private _hasWelcomeScreen(): boolean { + return !this._terminalService.isProcessSupportRegistered; + } + override shouldShowWelcome(): boolean { - this._isWelcomeShowing = !this._terminalService.isProcessSupportRegistered && this._terminalService.instances.length === 0; - return this._isWelcomeShowing; + return this._hasWelcomeScreen() && this._terminalService.instances.length === 0; } } diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts index 07ec5631f32..6b1fb0e5467 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/terminalCapabilityStore.test.ts @@ -22,6 +22,8 @@ suite('TerminalCapabilityStore', () => { removeEvents = []; }); + ensureNoDisposablesAreLeakedInTestSuite(); + teardown(() => store.dispose()); test('should fire events when capabilities are added', () => { diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts index fb5b757d2b8..28929a64036 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/textAreaSyncAddon.ts @@ -6,10 +6,11 @@ import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { ITerminalLogService } from 'vs/platform/terminal/common/terminal'; +import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import type { Terminal, ITerminalAddon } from 'xterm'; import { debounce } from 'vs/base/common/decorators'; import { addDisposableListener } from 'vs/base/browser/dom'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface ITextAreaData { content: string; @@ -24,7 +25,7 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { activate(terminal: Terminal): void { this._terminal = terminal; - if (this._accessibilityService.isScreenReaderOptimized()) { + if (this._shouldBeActive()) { this._registerSyncListeners(); } } @@ -32,11 +33,12 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { constructor( private readonly _capabilities: ITerminalCapabilityStore, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalLogService private readonly _logService: ITerminalLogService ) { super(); this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => { - if (this._accessibilityService.isScreenReaderOptimized()) { + if (this._shouldBeActive()) { this._syncTextArea(); this._registerSyncListeners(); } else { @@ -46,7 +48,7 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { } private _registerSyncListeners(): void { - if (this._accessibilityService.isScreenReaderOptimized() && this._terminal?.textarea) { + if (this._shouldBeActive() && this._terminal?.textarea) { this._listeners.value = new DisposableStore(); this._listeners.value.add(this._terminal.onCursorMove(() => this._syncTextArea())); this._listeners.value.add(this._terminal.onData(() => this._syncTextArea())); @@ -54,6 +56,10 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon { } } + private _shouldBeActive(): boolean { + return this._accessibilityService.isScreenReaderOptimized() || this._configurationService.getValue(TerminalSettingId.DevMode); + } + @debounce(50) private _syncTextArea(): void { this._logService.debug('TextAreaSyncAddon#syncTextArea'); diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 4d828a105ce..58699fc7b6d 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -15,7 +15,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IInternalXtermTerminal, ITerminalContribution, ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IInternalXtermTerminal, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { registerTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; @@ -118,7 +118,8 @@ class DevModeContribution extends DisposableStore implements ITerminalContributi instance: ITerminalInstance, processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, - @IConfigurationService private readonly _configurationService: IConfigurationService) { + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITerminalService private readonly _terminalService: ITerminalService) { super(); this.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalSettingId.DevMode)) { @@ -134,6 +135,11 @@ class DevModeContribution extends DisposableStore implements ITerminalContributi private _updateDevMode() { const devMode: boolean = this._configurationService.getValue(TerminalSettingId.DevMode) || false; this._xterm?.raw.element?.classList.toggle('dev-mode', devMode); + if (this._xterm?.raw.textarea) { + const font = this._terminalService.configHelper.getFont(); + this._xterm.raw.textarea.style.fontFamily = font.fontFamily; + this._xterm.raw.textarea.style.fontSize = `${font.fontSize}px`; + } } } registerTerminalContribution(DevModeContribution.ID, DevModeContribution); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index ade1e1091e3..072fa071e36 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1534,7 +1534,7 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { private attachTerminalToDom(terminal: IDetachedTerminalInstance) { terminal.xterm.write('\x1b[?25l'); // hide cursor - requestAnimationFrame(() => this.layoutTerminal(terminal)); + dom.scheduleAtNextAnimationFrame(() => this.layoutTerminal(terminal), dom.getWindow(this.container)); terminal.attachToElement(this.container, { enableGpu: false }); } diff --git a/src/vs/workbench/electron-sandbox/actions/windowActions.ts b/src/vs/workbench/electron-sandbox/actions/windowActions.ts index 2e1902cd787..71208a301b0 100644 --- a/src/vs/workbench/electron-sandbox/actions/windowActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/windowActions.ts @@ -250,7 +250,7 @@ abstract class BaseSwitchWindow extends Action2 { for (const window of mainWindows) { const auxiliaryWindows = mapMainWindowToAuxiliaryWindows.get(window.id); if (mapMainWindowToAuxiliaryWindows.size > 0) { - picks.push({ type: 'separator', payload: -1, label: auxiliaryWindows ? localize('windowGroup', "Window Group") : undefined } as unknown as IWindowPickItem); + picks.push({ type: 'separator', payload: -1, label: auxiliaryWindows ? localize('windowGroup', "window group") : undefined } as unknown as IWindowPickItem); } const resource = window.filename ? URI.file(window.filename) : isSingleFolderWorkspaceIdentifier(window.workspace) ? window.workspace.uri : isWorkspaceIdentifier(window.workspace) ? window.workspace.configPath : undefined; diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index d33a776105a..aeec64376e7 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -15,7 +15,7 @@ import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHos import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { isAuxiliaryWindow } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService'; -import { getActiveDocument, getWindowsCount, onDidRegisterWindow, trackFocus } from 'vs/base/browser/dom'; +import { getActiveDocument, getWindowsCount, onDidRegisterWindow, scheduleAtNextAnimationFrame, trackFocus } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { memoize } from 'vs/base/common/decorators'; @@ -59,6 +59,14 @@ class WorkbenchHostService extends Disposable implements IHostService { disposables.add(focusTracker.onDidFocus(() => emitter.fire(this.hasFocus))); disposables.add(focusTracker.onDidBlur(() => emitter.fire(this.hasFocus))); disposables.add(onVisibilityChange.event(() => emitter.fire(this.hasFocus))); + + // Workaround: the window does not immediately seem to have focus when + // opening, so we schedule a check for focus on the next animation frame + scheduleAtNextAnimationFrame(() => { + if (window.document.hasFocus()) { + emitter.fire(true); + } + }, window); })); return emitter.event; diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 7c44a06d34a..fdd52671245 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -25,6 +25,8 @@ export const enum Parts { export const enum LayoutSettings { ACTIVITY_BAR_LOCATION = 'workbench.activityBar.location', + EDITOR_TABS_MODE = 'workbench.editor.showTabs', + COMMAND_CENTER = 'window.commandCenter', } export const enum ActivityBarPosition { @@ -33,6 +35,12 @@ export const enum ActivityBarPosition { HIDDEN = 'hidden' } +export const enum EditorTabsMode { + MULTIPLE = 'multiple', + SINGLE = 'single', + NONE = 'none' +} + export const enum Position { LEFT, RIGHT, @@ -175,6 +183,7 @@ export interface IWorkbenchLayoutService extends ILayoutService { /** * Returns the parts HTML element, if there is one. */ + getContainer(window: Window): HTMLElement; getContainer(part: Parts): HTMLElement | undefined; /** diff --git a/src/vs/workbench/services/search/common/searchHelpers.ts b/src/vs/workbench/services/search/common/searchHelpers.ts index 50e8d7a43ca..3f63134b5bf 100644 --- a/src/vs/workbench/services/search/common/searchHelpers.ts +++ b/src/vs/workbench/services/search/common/searchHelpers.ts @@ -44,7 +44,7 @@ export function editorMatchesToTextSearchResults(matches: FindMatch[], model: IT }); } -export function addContextToEditorMatches(matches: ITextSearchMatch[], model: ITextModel, query: ITextQuery): ITextSearchResult[] { +export function getTextSearchMatchWithModelContext(matches: ITextSearchMatch[], model: ITextModel, query: ITextQuery): ITextSearchResult[] { const results: ITextSearchResult[] = []; let prevLine = -1; @@ -55,7 +55,7 @@ export function addContextToEditorMatches(matches: ITextSearchMatch[], model: IT for (let b = beforeContextStartLine; b < matchStartLine; b++) { results.push({ text: model.getLineContent(b + 1), - lineNumber: b + lineNumber: b + 1 }); } } @@ -69,7 +69,7 @@ export function addContextToEditorMatches(matches: ITextSearchMatch[], model: IT for (let a = matchEndLine + 1; a <= afterContextToLine; a++) { results.push({ text: model.getLineContent(a + 1), - lineNumber: a + lineNumber: a + 1 }); } } diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 4e8fb9a5805..ac934456627 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -22,7 +22,7 @@ import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/ed import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, isFileMatch, isProgressMessage, ITextQuery, pathIncludedInQuery, QueryType, SEARCH_RESULT_LANGUAGE_ID, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search'; -import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; +import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class SearchService extends Disposable implements ISearchService { @@ -494,7 +494,7 @@ export class SearchService extends Disposable implements ISearchService { openEditorResults.set(originalResource, fileMatch); const textSearchResults = editorMatchesToTextSearchResults(matches, model, query.previewOptions); - fileMatch.results = addContextToEditorMatches(textSearchResults, model, query); + fileMatch.results = getTextSearchMatchWithModelContext(textSearchResults, model, query); } else { openEditorResults.set(originalResource, null); } diff --git a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts index f22e14e9c76..2e09c9c6aea 100644 --- a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts +++ b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { FindMatch, ITextModel } from 'vs/editor/common/model'; import { ISearchRange, ITextQuery, ITextSearchContext, QueryType } from 'vs/workbench/services/search/common/search'; -import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; +import { getTextSearchMatchWithModelContext, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; suite('SearchHelpers', () => { suite('editorMatchesToTextSearchResults', () => { @@ -107,7 +107,7 @@ suite('SearchHelpers', () => { ranges: new Range(0, 0, 0, 10) }]; - assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery()), matches); + assert.deepStrictEqual(getTextSearchMatchWithModelContext(matches, mockTextModel, getQuery()), matches); }); test('simple', () => { @@ -119,19 +119,19 @@ suite('SearchHelpers', () => { ranges: new Range(1, 0, 1, 10) }]; - assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(getTextSearchMatchWithModelContext(matches, mockTextModel, getQuery(1, 2)), [ { text: '1', - lineNumber: 0 + lineNumber: 1 }, ...matches, { text: '3', - lineNumber: 2 + lineNumber: 3 }, { text: '4', - lineNumber: 3 + lineNumber: 4 }, ]); }); @@ -153,19 +153,19 @@ suite('SearchHelpers', () => { ranges: new Range(2, 0, 2, 10) }]; - assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(getTextSearchMatchWithModelContext(matches, mockTextModel, getQuery(1, 2)), [ { text: '1', - lineNumber: 0 + lineNumber: 1 }, ...matches, { text: '4', - lineNumber: 3 + lineNumber: 4 }, { text: '5', - lineNumber: 4 + lineNumber: 5 }, ]); }); @@ -187,19 +187,19 @@ suite('SearchHelpers', () => { ranges: new Range(MOCK_LINE_COUNT - 1, 0, MOCK_LINE_COUNT - 1, 10) }]; - assert.deepStrictEqual(addContextToEditorMatches(matches, mockTextModel, getQuery(1, 2)), [ + assert.deepStrictEqual(getTextSearchMatchWithModelContext(matches, mockTextModel, getQuery(1, 2)), [ matches[0], { text: '2', - lineNumber: 1 - }, - { - text: '3', lineNumber: 2 }, + { + text: '3', + lineNumber: 3 + }, { text: '' + (MOCK_LINE_COUNT - 1), - lineNumber: MOCK_LINE_COUNT - 2 + lineNumber: MOCK_LINE_COUNT - 1 }, matches[1] ]); diff --git a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts index 7df145abc38..f3e8d1c542c 100644 --- a/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts +++ b/src/vs/workbench/services/suggest/browser/simpleSuggestWidget.ts @@ -256,7 +256,7 @@ export class SimpleSuggestWidget implements IDisposable { this._layout(this.element.size); // Reset focus border // this._details.widget.domNode.classList.remove('focused'); - }); + }, dom.getWindow(this.element.domNode)); } setLineContext(lineContext: LineContext): void { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index 9e10ec35371..aba2a967b4b 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -672,12 +672,14 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } else { sessionId = (await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes)).id; } + this.currentAuthenticationProviderId = accountOrAuthProvider.id; } else { if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.id === accountOrAuthProvider.authenticationProviderId) { sessionId = await this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.signIn(); } else { sessionId = accountOrAuthProvider.sessionId; } + this.currentAuthenticationProviderId = accountOrAuthProvider.authenticationProviderId; } this.currentSessionId = sessionId; await this.update(); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index a4e3c43859d..abffa33779a 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -623,7 +623,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { getWindowBorderRadius(): string | undefined { return undefined; } isVisible(_part: Parts): boolean { return true; } getDimension(_part: Parts): Dimension { return new Dimension(0, 0); } - getContainer(_part: Parts): HTMLElement { return null!; } + getContainer(): HTMLElement { return null!; } isTitleBarHidden(): boolean { return false; } isStatusBarHidden(): boolean { return false; } isActivityBarHidden(): boolean { return false; } diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index 83e7a76ace7..e06eaaf5be4 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -119,6 +119,7 @@ export class Application { // We need a rendered workbench await measureAndLog(() => code.didFinishLoad(), 'Application#checkWindowReady: wait for navigation to be committed', this.logger); await measureAndLog(() => code.waitForElement('.monaco-workbench'), 'Application#checkWindowReady: wait for .monaco-workbench element', this.logger); + await measureAndLog(() => code.whenWorkbenchRestored(), 'Application#checkWorkbenchRestored', this.logger); // Remote but not web: wait for a remote connection state change if (this.remote) { diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 48cf1be0c53..ce4f14aa889 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -242,8 +242,14 @@ export class Code { await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`); } - whenWorkbenchRestored(): Promise { - return this.driver.whenWorkbenchRestored(); + async whenWorkbenchRestored(): Promise { + try { + await this.poll(() => this.driver.whenWorkbenchRestored(), () => true, `when workbench restored`); + } catch (error) { + // TODO: @sandy081 Remove this when 1.84.0 is out + // whenWorkbenchRestored was not implemented in the driver before 1.84.0 + this.logger.log('whenWorkbenchRestored() timed out'); + } } getLocaleInfo(): Promise { diff --git a/test/automation/src/quickaccess.ts b/test/automation/src/quickaccess.ts index b14197b4f4b..5d94a96f1c4 100644 --- a/test/automation/src/quickaccess.ts +++ b/test/automation/src/quickaccess.ts @@ -198,9 +198,6 @@ export class QuickAccess { this.code.logger.log(`QuickAccess: No matching commands, will retry...`); await this.quickInput.closeQuickInput(); - // Wait for workbench to be restored - await this.code.whenWorkbenchRestored(); - let retries = 0; while (++retries < 5) { hasCommandFound = await openCommandPalletteAndTypeCommand(); @@ -218,7 +215,6 @@ export class QuickAccess { } } - // wait and click on best choice await this.quickInput.selectQuickInputElement(0, keepOpen); } diff --git a/yarn.lock b/yarn.lock index ec86215b388..32b1df8ced5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10368,45 +10368,45 @@ xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-canvas@0.6.0-beta.32: - version "0.6.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.32.tgz#c9e74dd72fcc981a2e0cbd0b82827676bc5c74b9" - integrity sha512-Xw7oE4dbS+x+pu6cGW1bDSXcVviuorLz1OLaYw46jjmDezIqQIIEMhSMOprExFEWgeRQ9AEN4lPqw6aH87V74w== +xterm-addon-canvas@0.6.0-beta.37: + version "0.6.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.6.0-beta.37.tgz#4f7692fc87f3500a5a6e7f822ec74796e6a36db2" + integrity sha512-eFN7iPxqiQosUO+rbXKwPz/AR3TLJcLCVWjvT5EaewqNpw+E9rWVwRRPGqYSw+qkSKM/vcNlySIkq4hmpA/ohg== 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.31: - version "0.14.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.31.tgz#933ca5d2d642dacad29f2cfbd50830cff83bc274" - integrity sha512-JRY1ukhoh32D0AMz78xpumQkLgkcP9d3GXj6gzVHZZsjLAMDaJYEubYq1bUhM7IGHUyg+x0sdRJyx7d6fJpiQg== +xterm-addon-search@0.14.0-beta.36: + version "0.14.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.14.0-beta.36.tgz#4bb161130c39090d506f73468ef65054aedcb9f0" + integrity sha512-2ydZV1Nt8d0vOjZ/y2RXiw3QLgabK+PCA7yETqZNivnmhSfcxtYPyI4aRfG/lc3hoMKiPppSSjJF0MlV/qo8ww== -xterm-addon-serialize@0.12.0-beta.31: - version "0.12.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.31.tgz#2a95dc1e12f4097e2894b04c9cb8fff0bc0b858c" - integrity sha512-h2rWR+Lfi1Iv4VkLUlrBMYh5Mdq8vux2BKyCJe6a1ZnEu5Dzb0VuiNxfTKXTCT5M83nMn7TCB9TX0E8z6bs7xw== +xterm-addon-serialize@0.12.0-beta.36: + version "0.12.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.12.0-beta.36.tgz#8e74159317e2c1e5f2724fd92b85df2b87d2c9c9" + integrity sha512-J2mr3Wuxzvxke14IpfJBhwCHlWoB428FE06tOfbGi5lCBm0AnO2cIyRymvgVZG4QVXVxzzWroE8wP/WQQVe45w== -xterm-addon-unicode11@0.7.0-beta.31: - version "0.7.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.31.tgz#abcba752172323f31312bd8a3f9b6a049dbca6e3" - integrity sha512-vvBKJbBoLbeIf2++6D16VnOOwevZE3nyO/PDZ7cyTJK1eYR73rr8ZbjUrH92YoTu4Z8MpZFepGQOgK/vlAQMwQ== +xterm-addon-unicode11@0.7.0-beta.36: + version "0.7.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.7.0-beta.36.tgz#bde0b8c98211b74b0288cd5bad09107384b870ca" + integrity sha512-vV/f7dTP/46JjoJLYDCxolCWPoUn0cF7S5upYvY0ZA1VLQ9UnXmaNjzrg+9f+QBoXEQ2eQDLQcRFWOc6BAbF+A== -xterm-addon-webgl@0.17.0-beta.31: - version "0.17.0-beta.31" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.31.tgz#3cd29b4858e3f4f6dd5a8dd969454e85e1f43baa" - integrity sha512-vYHj+HlTcqUlFFVuoCTjlgh89/lIoSkZ7Nc87cwSFTrJsl07qoKutmpupqFXyjhbEA1fQY2SuQLx08Gmf2jWkQ== +xterm-addon-webgl@0.17.0-beta.36: + version "0.17.0-beta.36" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.17.0-beta.36.tgz#6ed16a83cd4bed3cbddd631e7974375aa8f7e023" + integrity sha512-5PNQZcNH2UTaqSf7Exgh/fvsNeTOs+CSEuIaTafzKuMIrWH14+cRdyJdzyo8OKTNzhJOjwWDOTQTD/sur4lrLg== -xterm-headless@5.4.0-beta.32: - version "5.4.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.32.tgz#0d5cd35e1a0372888055ff0b06dfe17457979a6c" - integrity sha512-DQduq8KSoQZyRrQAFB+FkcY2UMxCW39P1/duOpksebc6PT9pbGkyPe5s+AdUQGiYzriEpzVtKUzDcquoVmpPhA== +xterm-headless@5.4.0-beta.37: + version "5.4.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.4.0-beta.37.tgz#1e89dfa7e667a5951974a9e9b5e1b0875198ca65" + integrity sha512-iP28Z419p7anaNetLhmPOpt/DUdeA45jVLqoTn8bRpwFj8kM/gI76tUO9y/TyMfv79yWwPv2MrkALfS6ZyTi7g== -xterm@5.4.0-beta.32: - version "5.4.0-beta.32" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.32.tgz#1b4242cf1c0c1a5a1070da58d3f11956b537130a" - integrity sha512-mWTwEiNBFMF89oqVfi6qTM2Py5gC1Mwvslx1KxmI2Ukgh9v3CrqKDhj29eY1ZeAo0uuYknFWKyuexqp+3SHJCA== +xterm@5.4.0-beta.37: + version "5.4.0-beta.37" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.4.0-beta.37.tgz#bae127be8c939f096232d9858e77a457d6b77ddf" + integrity sha512-ys+mXqLFrJc7khmYN/MgBnfLv38NgXfkwkEXsCZKHGqn3h2xUBvTvsrSEWO3NQeDPLj4zMr1RwqTblMK9St3BA== y18n@^3.2.1: version "3.2.2"